mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
Merge mozilla-central to b2g-inbound
This commit is contained in:
commit
c0f0b148a5
27
.ycm_extra_conf.py
Normal file
27
.ycm_extra_conf.py
Normal file
@ -0,0 +1,27 @@
|
||||
# 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/.
|
||||
|
||||
import imp
|
||||
import os
|
||||
from StringIO import StringIO
|
||||
import shlex
|
||||
|
||||
path = os.path.join(os.path.dirname(__file__), 'mach')
|
||||
|
||||
if not os.path.exists(path):
|
||||
path = os.path.join(os.path.dirname(__file__), 'config.status')
|
||||
config = imp.load_module('_buildconfig', open(path), path, ('', 'r', imp.PY_SOURCE))
|
||||
path = os.path.join(config.topsrcdir, 'mach')
|
||||
mach_module = imp.load_module('_mach', open(path), path, ('', 'r', imp.PY_SOURCE))
|
||||
|
||||
def FlagsForFile(filename):
|
||||
mach = mach_module.get_mach()
|
||||
out = StringIO()
|
||||
out.encoding = None
|
||||
mach.run(['compileflags', filename], stdout=out, stderr=out)
|
||||
|
||||
return {
|
||||
'flags': shlex.split(out.getvalue()),
|
||||
'do_cache': True
|
||||
}
|
5
CLOBBER
5
CLOBBER
@ -22,7 +22,4 @@
|
||||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||
# don't change CLOBBER for WebIDL changes any more.
|
||||
|
||||
Bug 1038068: Check add-on signatures and refuse to install unsigned or broken add-ons
|
||||
|
||||
Not sure why this needs a clobber but tests perma-failed when they don't on
|
||||
try (2).
|
||||
Bug 1154356: escape variable name in Declaration::AppendVariableAndValueToString;
|
||||
|
@ -123,10 +123,6 @@ static const GInterfaceInfo atk_if_infos[] = {
|
||||
(GInterfaceFinalizeFunc) nullptr, nullptr}
|
||||
};
|
||||
|
||||
// This is or'd with the pointer in MaiAtkObject::accWrap if the wrap-ee is a
|
||||
// proxy.
|
||||
static const uintptr_t IS_PROXY = 1;
|
||||
|
||||
static GQuark quark_mai_hyperlink = 0;
|
||||
|
||||
AtkHyperlink*
|
||||
@ -136,7 +132,7 @@ MaiAtkObject::GetAtkHyperlink()
|
||||
MaiHyperlink* maiHyperlink =
|
||||
(MaiHyperlink*)g_object_get_qdata(G_OBJECT(this), quark_mai_hyperlink);
|
||||
if (!maiHyperlink) {
|
||||
maiHyperlink = new MaiHyperlink(reinterpret_cast<Accessible*>(accWrap));
|
||||
maiHyperlink = new MaiHyperlink(accWrap);
|
||||
g_object_set_qdata(G_OBJECT(this), quark_mai_hyperlink, maiHyperlink);
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,10 @@ IsAtkVersionAtLeast(int aMajor, int aMinor)
|
||||
(aMajor == atkMajorVersion && aMinor <= atkMinorVersion);
|
||||
}
|
||||
|
||||
// This is or'd with the pointer in MaiAtkObject::accWrap if the wrap-ee is a
|
||||
// proxy.
|
||||
static const uintptr_t IS_PROXY = 1;
|
||||
|
||||
/**
|
||||
* This MaiAtkObject is a thin wrapper, in the MAI namespace, for AtkObject
|
||||
*/
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "nsIURI.h"
|
||||
#include "nsMaiHyperlink.h"
|
||||
#include "mozilla/a11y/ProxyAccessible.h"
|
||||
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
@ -61,8 +62,17 @@ static gint getAnchorCountCB(AtkHyperlink *aLink);
|
||||
G_END_DECLS
|
||||
|
||||
static gpointer parent_class = nullptr;
|
||||
static Accessible*
|
||||
get_accessible_hyperlink(AtkHyperlink *aHyperlink);
|
||||
|
||||
static MaiHyperlink*
|
||||
GetMaiHyperlink(AtkHyperlink *aHyperlink)
|
||||
{
|
||||
NS_ENSURE_TRUE(MAI_IS_ATK_HYPERLINK(aHyperlink), nullptr);
|
||||
MaiHyperlink * maiHyperlink =
|
||||
MAI_ATK_HYPERLINK(aHyperlink)->maiHyperlink;
|
||||
NS_ENSURE_TRUE(maiHyperlink != nullptr, nullptr);
|
||||
NS_ENSURE_TRUE(maiHyperlink->GetAtkHyperlink() == aHyperlink, nullptr);
|
||||
return maiHyperlink;
|
||||
}
|
||||
|
||||
GType
|
||||
mai_atk_hyperlink_get_type(void)
|
||||
@ -90,13 +100,10 @@ mai_atk_hyperlink_get_type(void)
|
||||
return type;
|
||||
}
|
||||
|
||||
MaiHyperlink::MaiHyperlink(Accessible* aHyperLink) :
|
||||
MaiHyperlink::MaiHyperlink(uintptr_t aHyperLink) :
|
||||
mHyperlink(aHyperLink),
|
||||
mMaiAtkHyperlink(nullptr)
|
||||
{
|
||||
if (!mHyperlink->IsLink())
|
||||
return;
|
||||
|
||||
mMaiAtkHyperlink =
|
||||
reinterpret_cast<AtkHyperlink *>
|
||||
(g_object_new(mai_atk_hyperlink_get_type(), nullptr));
|
||||
@ -153,79 +160,102 @@ finalizeCB(GObject *aObj)
|
||||
gchar *
|
||||
getUriCB(AtkHyperlink *aLink, gint aLinkIndex)
|
||||
{
|
||||
Accessible* hyperlink = get_accessible_hyperlink(aLink);
|
||||
NS_ENSURE_TRUE(hyperlink, nullptr);
|
||||
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
|
||||
if (!maiLink)
|
||||
return nullptr;
|
||||
|
||||
nsAutoCString cautoStr;
|
||||
if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
|
||||
nsCOMPtr<nsIURI> uri = hyperlink->AnchorURIAt(aLinkIndex);
|
||||
if (!uri)
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
|
||||
nsAutoCString cautoStr;
|
||||
nsresult rv = uri->GetSpec(cautoStr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
return g_strdup(cautoStr.get());
|
||||
}
|
||||
|
||||
bool valid;
|
||||
maiLink->Proxy()->AnchorURIAt(aLinkIndex, cautoStr, &valid);
|
||||
if (!valid)
|
||||
return nullptr;
|
||||
|
||||
return g_strdup(cautoStr.get());
|
||||
}
|
||||
|
||||
AtkObject *
|
||||
getObjectCB(AtkHyperlink *aLink, gint aLinkIndex)
|
||||
{
|
||||
Accessible* hyperlink = get_accessible_hyperlink(aLink);
|
||||
NS_ENSURE_TRUE(hyperlink, nullptr);
|
||||
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
|
||||
if (!maiLink)
|
||||
return nullptr;
|
||||
|
||||
Accessible* anchor = hyperlink->AnchorAt(aLinkIndex);
|
||||
NS_ENSURE_TRUE(anchor, nullptr);
|
||||
if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
|
||||
Accessible* anchor = hyperlink->AnchorAt(aLinkIndex);
|
||||
NS_ENSURE_TRUE(anchor, nullptr);
|
||||
|
||||
AtkObject* atkObj = AccessibleWrap::GetAtkObject(anchor);
|
||||
//no need to add ref it, because it is "get" not "ref"
|
||||
return atkObj;
|
||||
return AccessibleWrap::GetAtkObject(anchor);
|
||||
}
|
||||
|
||||
ProxyAccessible* anchor = maiLink->Proxy()->AnchorAt(aLinkIndex);
|
||||
return anchor ? GetWrapperFor(anchor) : nullptr;
|
||||
}
|
||||
|
||||
gint
|
||||
getEndIndexCB(AtkHyperlink *aLink)
|
||||
{
|
||||
Accessible* hyperlink = get_accessible_hyperlink(aLink);
|
||||
NS_ENSURE_TRUE(hyperlink, -1);
|
||||
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
|
||||
if (!maiLink)
|
||||
return false;
|
||||
|
||||
if (Accessible* hyperlink = maiLink->GetAccHyperlink())
|
||||
return static_cast<gint>(hyperlink->EndOffset());
|
||||
|
||||
bool valid = false;
|
||||
uint32_t endIdx = maiLink->Proxy()->EndOffset(&valid);
|
||||
return valid ? static_cast<gint>(endIdx) : -1;
|
||||
}
|
||||
|
||||
gint
|
||||
getStartIndexCB(AtkHyperlink *aLink)
|
||||
{
|
||||
Accessible* hyperlink = get_accessible_hyperlink(aLink);
|
||||
NS_ENSURE_TRUE(hyperlink, -1);
|
||||
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
|
||||
if (maiLink)
|
||||
return -1;
|
||||
|
||||
if (Accessible* hyperlink = maiLink->GetAccHyperlink())
|
||||
return static_cast<gint>(hyperlink->StartOffset());
|
||||
|
||||
bool valid = false;
|
||||
uint32_t startIdx = maiLink->Proxy()->StartOffset(&valid);
|
||||
return valid ? static_cast<gint>(startIdx) : -1;
|
||||
}
|
||||
|
||||
gboolean
|
||||
isValidCB(AtkHyperlink *aLink)
|
||||
{
|
||||
Accessible* hyperlink = get_accessible_hyperlink(aLink);
|
||||
NS_ENSURE_TRUE(hyperlink, FALSE);
|
||||
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
|
||||
if (!maiLink)
|
||||
return false;
|
||||
|
||||
if (Accessible* hyperlink = maiLink->GetAccHyperlink())
|
||||
return static_cast<gboolean>(hyperlink->IsLinkValid());
|
||||
|
||||
return static_cast<gboolean>(maiLink->Proxy()->IsLinkValid());
|
||||
}
|
||||
|
||||
gint
|
||||
getAnchorCountCB(AtkHyperlink *aLink)
|
||||
{
|
||||
Accessible* hyperlink = get_accessible_hyperlink(aLink);
|
||||
NS_ENSURE_TRUE(hyperlink, -1);
|
||||
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
|
||||
if (!maiLink)
|
||||
return -1;
|
||||
|
||||
if (Accessible* hyperlink = maiLink->GetAccHyperlink())
|
||||
return static_cast<gint>(hyperlink->AnchorCount());
|
||||
}
|
||||
|
||||
// Check if aHyperlink is a valid MaiHyperlink, and return the
|
||||
// HyperLinkAccessible related.
|
||||
Accessible*
|
||||
get_accessible_hyperlink(AtkHyperlink *aHyperlink)
|
||||
{
|
||||
NS_ENSURE_TRUE(MAI_IS_ATK_HYPERLINK(aHyperlink), nullptr);
|
||||
MaiHyperlink * maiHyperlink =
|
||||
MAI_ATK_HYPERLINK(aHyperlink)->maiHyperlink;
|
||||
NS_ENSURE_TRUE(maiHyperlink != nullptr, nullptr);
|
||||
NS_ENSURE_TRUE(maiHyperlink->GetAtkHyperlink() == aHyperlink, nullptr);
|
||||
return maiHyperlink->GetAccHyperlink();
|
||||
bool valid = false;
|
||||
uint32_t anchorCount = maiLink->Proxy()->AnchorCount(&valid);
|
||||
return valid ? static_cast<gint>(anchorCount) : -1;
|
||||
}
|
||||
|
@ -23,16 +23,31 @@ namespace a11y {
|
||||
class MaiHyperlink
|
||||
{
|
||||
public:
|
||||
explicit MaiHyperlink(Accessible* aHyperLink);
|
||||
explicit MaiHyperlink(uintptr_t aHyperLink);
|
||||
~MaiHyperlink();
|
||||
|
||||
public:
|
||||
AtkHyperlink* GetAtkHyperlink() const { return mMaiAtkHyperlink; }
|
||||
Accessible* GetAccHyperlink()
|
||||
{ return mHyperlink && mHyperlink->IsLink() ? mHyperlink : nullptr; }
|
||||
{
|
||||
if (!mHyperlink || mHyperlink & IS_PROXY)
|
||||
return nullptr;
|
||||
|
||||
Accessible* link = reinterpret_cast<Accessible*>(mHyperlink);
|
||||
NS_ASSERTION(link->IsLink(), "Why isn't it a link!");
|
||||
return link;
|
||||
}
|
||||
|
||||
ProxyAccessible* Proxy() const
|
||||
{
|
||||
if (!(mHyperlink & IS_PROXY))
|
||||
return nullptr;
|
||||
|
||||
return reinterpret_cast<ProxyAccessible*>(mHyperlink & ~IS_PROXY);
|
||||
}
|
||||
|
||||
protected:
|
||||
Accessible* mHyperlink;
|
||||
uintptr_t mHyperlink;
|
||||
AtkHyperlink* mMaiAtkHyperlink;
|
||||
};
|
||||
|
||||
|
@ -16,10 +16,11 @@ static AtkHyperlink*
|
||||
getHyperlinkCB(AtkHyperlinkImpl* aImpl)
|
||||
{
|
||||
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImpl));
|
||||
if (!accWrap)
|
||||
if (!accWrap || !GetProxy(ATK_OBJECT(aImpl)))
|
||||
return nullptr;
|
||||
|
||||
NS_ENSURE_TRUE(accWrap->IsLink(), nullptr);
|
||||
if (accWrap)
|
||||
NS_ASSERTION(accWrap->IsLink(), "why isn't it a link!");
|
||||
|
||||
return MAI_ATK_OBJECT(aImpl)->GetAtkHyperlink();
|
||||
}
|
||||
|
@ -22,31 +22,27 @@ static AtkHyperlink*
|
||||
getLinkCB(AtkHypertext *aText, gint aLinkIndex)
|
||||
{
|
||||
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
|
||||
AtkObject* atkHyperLink = nullptr;
|
||||
if (accWrap) {
|
||||
HyperTextAccessible* hyperText = accWrap->AsHyperText();
|
||||
NS_ENSURE_TRUE(hyperText, nullptr);
|
||||
|
||||
Accessible* hyperLink = hyperText->LinkAt(aLinkIndex);
|
||||
if (!hyperLink) {
|
||||
if (!hyperLink || !hyperLink->IsLink()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AtkObject* hyperLinkAtkObj = AccessibleWrap::GetAtkObject(hyperLink);
|
||||
NS_ENSURE_TRUE(IS_MAI_OBJECT(hyperLinkAtkObj), nullptr);
|
||||
|
||||
return MAI_ATK_OBJECT(hyperLinkAtkObj)->GetAtkHyperlink();
|
||||
}
|
||||
|
||||
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
|
||||
atkHyperLink = AccessibleWrap::GetAtkObject(hyperLink);
|
||||
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
|
||||
ProxyAccessible* proxyLink = proxy->LinkAt(aLinkIndex);
|
||||
if (proxyLink) {
|
||||
NS_WARNING("IMPLEMENT ME! See bug 1146518.");
|
||||
// We should somehow get from ProxyAccessible* to AtkHyperlink*.
|
||||
}
|
||||
return nullptr;
|
||||
if (!proxyLink)
|
||||
return nullptr;
|
||||
|
||||
atkHyperLink = GetWrapperFor(proxyLink);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
NS_ENSURE_TRUE(IS_MAI_OBJECT(atkHyperLink), nullptr);
|
||||
return MAI_ATK_OBJECT(atkHyperLink)->GetAtkHyperlink();
|
||||
}
|
||||
|
||||
static gint
|
||||
|
@ -1904,6 +1904,7 @@ pref("dom.serviceWorkers.enabled", true);
|
||||
#endif
|
||||
|
||||
pref("browser.pocket.enabled", false);
|
||||
pref("browser.pocket.hostname", "localhost");
|
||||
pref("browser.pocket.removedByUser", false);
|
||||
pref("browser.pocket.useLocaleList", true);
|
||||
pref("browser.pocket.enabledLocales", "en-US");
|
||||
|
@ -42,6 +42,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Log",
|
||||
"resource://gre/modules/Log.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
"resource://gre/modules/UpdateChannel.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
|
||||
"@mozilla.org/browser/favicon-service;1",
|
||||
"mozIAsyncFavicons");
|
||||
@ -2758,7 +2760,7 @@ let BrowserOnClick = {
|
||||
version: 1,
|
||||
build: gAppInfo.appBuildID,
|
||||
product: gAppInfo.name,
|
||||
channel: Services.prefs.getCharPref("app.update.channel")
|
||||
channel: UpdateChannel.get()
|
||||
}
|
||||
|
||||
let reportURL = Services.prefs.getCharPref("security.ssl.errorReporting.url");
|
||||
|
@ -18,12 +18,16 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
|
||||
"resource:///modules/PlacesUIUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
|
||||
"resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
|
||||
"resource:///modules/Pocket.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
|
||||
"resource://gre/modules/ShortcutUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
|
||||
"resource://gre/modules/CharsetMenu.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
|
||||
"resource://gre/modules/ReaderMode.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() {
|
||||
const kCharsetBundle = "chrome://global/locale/charsetMenu.properties";
|
||||
@ -1128,9 +1132,32 @@ if (Services.prefs.getBoolPref("browser.pocket.enabled")) {
|
||||
|
||||
let loginView = doc.getElementById("pocket-login-required");
|
||||
let pageSavedView = doc.getElementById("pocket-page-saved");
|
||||
let showPageSaved = Math.random() < 0.5;
|
||||
loginView.hidden = !showPageSaved;
|
||||
pageSavedView.hidden = showPageSaved;
|
||||
let showPageSaved = Pocket.isLoggedIn;
|
||||
loginView.hidden = showPageSaved;
|
||||
pageSavedView.hidden = !showPageSaved;
|
||||
|
||||
if (!showPageSaved)
|
||||
return;
|
||||
|
||||
let gBrowser = doc.defaultView.gBrowser;
|
||||
let uri = gBrowser.currentURI;
|
||||
if (uri.schemeIs("about"))
|
||||
uri = ReaderMode.getOriginalUrl(uri.spec);
|
||||
else
|
||||
uri = uri.spec;
|
||||
if (!uri)
|
||||
return; //TODO should prevent the panel from showing
|
||||
|
||||
Pocket.save(uri, gBrowser.contentTitle).then(
|
||||
item => {
|
||||
doc.getElementById("pocket-remove-page").itemId = item.item_id;
|
||||
},
|
||||
error => {dump(error + "\n");}
|
||||
);
|
||||
},
|
||||
onViewHiding(event) {
|
||||
let doc = event.target.ownerDocument;
|
||||
doc.getElementById("pocket-remove-page").itemId = null;
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
@ -1142,7 +1169,8 @@ if (Services.prefs.getBoolPref("browser.pocket.enabled")) {
|
||||
button.disabled = !field.value.trim();
|
||||
break;
|
||||
case "command":
|
||||
//XXXjaws Send tag to the Pocket server
|
||||
Pocket.tag(doc.getElementById("pocket-remove-page").itemId,
|
||||
field.value);
|
||||
field.value = "";
|
||||
break;
|
||||
}
|
||||
|
@ -246,7 +246,8 @@
|
||||
<label id="pocket-page-saved-header" class="pocket-header"/>
|
||||
<hbox id="pocket-page-saved-next-steps" pack="center">
|
||||
<label id="pocket-open-pocket" class="text-link"/>
|
||||
<label id="pocket-remove-page" class="text-link"/>
|
||||
<label id="pocket-remove-page" class="text-link"
|
||||
onclick="Pocket.remove(this.itemId);"/>
|
||||
</hbox>
|
||||
<hbox id="pocket-separator">
|
||||
<box class="pocket-separator-colorstop"/>
|
||||
|
@ -6,10 +6,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
||||
"resource:///modules/CustomizableUI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
|
||||
"resource:///modules/ScrollbarSampler.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
|
||||
"resource:///modules/Pocket.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
|
||||
"resource://gre/modules/ShortcutUtils.jsm");
|
||||
|
||||
/**
|
||||
* Maintains the state and dispatches events for the main menu panel.
|
||||
*/
|
||||
@ -404,7 +407,7 @@ const PanelUI = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Signal that we're about to make a lot of changes to the contents of the
|
||||
* panels all at once. For performance, we ignore the mutations.
|
||||
*/
|
||||
@ -527,4 +530,3 @@ function getLocale() {
|
||||
|
||||
return "en-US";
|
||||
}
|
||||
|
||||
|
@ -265,10 +265,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
mozL10n.get("share_button3")
|
||||
)
|
||||
),
|
||||
React.createElement(SocialShareDropdown, {dispatcher: this.props.dispatcher,
|
||||
roomUrl: this.props.roomData.roomUrl,
|
||||
show: this.state.showMenu,
|
||||
ref: "menu"})
|
||||
React.createElement(SocialShareDropdown, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
roomUrl: this.props.roomData.roomUrl,
|
||||
show: this.state.showMenu,
|
||||
socialShareButtonAvailable: this.props.socialShareButtonAvailable,
|
||||
socialShareProviders: this.props.socialShareProviders,
|
||||
ref: "menu"})
|
||||
),
|
||||
React.createElement(DesktopRoomContextView, {
|
||||
roomData: this.props.roomData,
|
||||
@ -443,7 +446,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
error: this.state.error,
|
||||
roomData: roomData,
|
||||
show: shouldRenderInvitationOverlay,
|
||||
showContext: shouldRenderContextView}),
|
||||
showContext: shouldRenderContextView,
|
||||
socialShareButtonAvailable: this.state.socialShareButtonAvailable,
|
||||
socialShareProviders: this.state.socialShareProviders}),
|
||||
React.createElement("div", {className: "video-layout-wrapper"},
|
||||
React.createElement("div", {className: "conversation room-conversation"},
|
||||
React.createElement("div", {className: "media nested"},
|
||||
|
@ -265,10 +265,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
{mozL10n.get("share_button3")}
|
||||
</button>
|
||||
</div>
|
||||
<SocialShareDropdown dispatcher={this.props.dispatcher}
|
||||
roomUrl={this.props.roomData.roomUrl}
|
||||
show={this.state.showMenu}
|
||||
ref="menu"/>
|
||||
<SocialShareDropdown
|
||||
dispatcher={this.props.dispatcher}
|
||||
roomUrl={this.props.roomData.roomUrl}
|
||||
show={this.state.showMenu}
|
||||
socialShareButtonAvailable={this.props.socialShareButtonAvailable}
|
||||
socialShareProviders={this.props.socialShareProviders}
|
||||
ref="menu" />
|
||||
</div>
|
||||
<DesktopRoomContextView
|
||||
roomData={this.props.roomData}
|
||||
@ -443,7 +446,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
error={this.state.error}
|
||||
roomData={roomData}
|
||||
show={shouldRenderInvitationOverlay}
|
||||
showContext={shouldRenderContextView} />
|
||||
showContext={shouldRenderContextView}
|
||||
socialShareButtonAvailable={this.state.socialShareButtonAvailable}
|
||||
socialShareProviders={this.state.socialShareProviders} />
|
||||
<div className="video-layout-wrapper">
|
||||
<div className="conversation room-conversation">
|
||||
<div className="media nested">
|
||||
|
@ -734,6 +734,7 @@ BrowserGlue.prototype = {
|
||||
LightweightThemeManager.addBuiltInTheme({
|
||||
id: "firefox-devedition@mozilla.org",
|
||||
name: themeName,
|
||||
accentcolor: "transparent",
|
||||
headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
|
||||
iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png",
|
||||
author: vendorShortName,
|
||||
|
109
browser/components/pocket/Pocket.jsm
Normal file
109
browser/components/pocket/Pocket.jsm
Normal file
@ -0,0 +1,109 @@
|
||||
/* 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";
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Pocket"];
|
||||
|
||||
Cu.import("resource://gre/modules/Http.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let Pocket = {
|
||||
get isLoggedIn() {
|
||||
return !!this._accessToken;
|
||||
},
|
||||
|
||||
prefBranch: Services.prefs.getBranch("browser.pocket.settings."),
|
||||
|
||||
get hostname() Services.prefs.getCharPref("browser.pocket.hostname"),
|
||||
|
||||
get _accessToken() {
|
||||
let sessionId, accessToken;
|
||||
let cookies = Services.cookies.getCookiesFromHost(this.hostname);
|
||||
while (cookies.hasMoreElements()) {
|
||||
let cookie = cookies.getNext().QueryInterface(Ci.nsICookie2);
|
||||
if (cookie.name == "ftv1")
|
||||
accessToken = cookie.value;
|
||||
else if (cookie.name == "fsv1")
|
||||
sessionId = cookie.value;
|
||||
}
|
||||
|
||||
if (!accessToken)
|
||||
return null;
|
||||
|
||||
let lastSessionId;
|
||||
try {
|
||||
lastSessionId = this.prefBranch.getCharPref("sessionId");
|
||||
} catch (e) { }
|
||||
if (sessionId != lastSessionId)
|
||||
this.prefBranch.deleteBranch("");
|
||||
this.prefBranch.setCharPref("sessionId", sessionId);
|
||||
|
||||
return accessToken;
|
||||
},
|
||||
|
||||
save(url, title) {
|
||||
let since = "0";
|
||||
try {
|
||||
since = this.prefBranch.getCharPref("latestSince");
|
||||
} catch (e) { }
|
||||
|
||||
let data = {url: url, since: since, title: title};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._send("firefox/save", data,
|
||||
data => {
|
||||
this.prefBranch.setCharPref("latestSince", data.since);
|
||||
resolve(data.item);
|
||||
},
|
||||
error => { reject(error); }
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
remove(itemId) {
|
||||
let actions = [{ action: "delete", item_id: itemId }];
|
||||
this._send("send", {actions: JSON.stringify(actions)});
|
||||
},
|
||||
|
||||
tag(itemId, tags) {
|
||||
let actions = [{ action: "tags_add", item_id: itemId, tags: tags }];
|
||||
this._send("send", {actions: JSON.stringify(actions)});
|
||||
},
|
||||
|
||||
_send(url, data, onSuccess, onError) {
|
||||
let token = this._accessToken;
|
||||
if (!token)
|
||||
throw "Attempted to send a request to Pocket while not logged in";
|
||||
|
||||
let browserLocale = Cc["@mozilla.org/chrome/chrome-registry;1"]
|
||||
.getService(Ci.nsIXULChromeRegistry)
|
||||
.getSelectedLocale("browser");
|
||||
|
||||
let postData = [
|
||||
["access_token", token],
|
||||
["consumer_key", "40249-e88c401e1b1f2242d9e441c4"],
|
||||
["locale_lang", browserLocale]
|
||||
];
|
||||
|
||||
for (let key in data)
|
||||
postData.push([key, data[key]]);
|
||||
|
||||
httpRequest("https://" + this.hostname + "/v3/" + url, {
|
||||
headers: [["X-Accept", "application/json"]],
|
||||
postData: postData,
|
||||
onLoad: (responseText) => {
|
||||
if (onSuccess)
|
||||
onSuccess(JSON.parse(responseText));
|
||||
},
|
||||
onError: function(error, responseText, xhr) {
|
||||
if (!onError)
|
||||
return;
|
||||
let errorMessage = xhr.getResponseHeader("X-Error");
|
||||
onError(new Error(error + " - " + errorMessage));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
@ -1,5 +1,7 @@
|
||||
# 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/.
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
# 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/.
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
EXTRA_JS_MODULES += ['Pocket.jsm']
|
||||
|
@ -30,6 +30,10 @@ this.SessionCookies = Object.freeze({
|
||||
|
||||
getHostsForWindow: function (window, checkPrivacy = false) {
|
||||
return SessionCookiesInternal.getHostsForWindow(window, checkPrivacy);
|
||||
},
|
||||
|
||||
restore(cookies) {
|
||||
SessionCookiesInternal.restore(cookies);
|
||||
}
|
||||
});
|
||||
|
||||
@ -107,6 +111,18 @@ let SessionCookiesInternal = {
|
||||
return hosts;
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores a given list of session cookies.
|
||||
*/
|
||||
restore(cookies) {
|
||||
for (let cookie of cookies) {
|
||||
let expiry = "expiry" in cookie ? cookie.expiry : MAX_EXPIRY;
|
||||
Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
|
||||
cookie.value, !!cookie.secure, !!cookie.httponly,
|
||||
/* isSession = */ true, expiry);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles observers notifications that are sent whenever cookies are added,
|
||||
* changed, or removed. Ensures that the storage is updated accordingly.
|
||||
|
@ -118,9 +118,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
|
||||
"@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
|
||||
"@mozilla.org/base/telemetry;1", "nsITelemetry");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
||||
"@mozilla.org/parentprocessmessagemanager;1",
|
||||
"nsIMessageListenerManager");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
||||
@ -1521,7 +1518,7 @@ let SessionStoreInternal = {
|
||||
// Don't include any deferred initial state.
|
||||
delete state.deferredInitialState;
|
||||
|
||||
return this._toJSONString(state);
|
||||
return JSON.stringify(state);
|
||||
},
|
||||
|
||||
setBrowserState: function ssi_setBrowserState(aState) {
|
||||
@ -1574,12 +1571,12 @@ let SessionStoreInternal = {
|
||||
|
||||
getWindowState: function ssi_getWindowState(aWindow) {
|
||||
if ("__SSi" in aWindow) {
|
||||
return this._toJSONString(this._getWindowState(aWindow));
|
||||
return JSON.stringify(this._getWindowState(aWindow));
|
||||
}
|
||||
|
||||
if (DyingWindowCache.has(aWindow)) {
|
||||
let data = DyingWindowCache.get(aWindow);
|
||||
return this._toJSONString({ windows: [data] });
|
||||
return JSON.stringify({ windows: [data] });
|
||||
}
|
||||
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
@ -1600,7 +1597,7 @@ let SessionStoreInternal = {
|
||||
|
||||
let tabState = TabState.collect(aTab);
|
||||
|
||||
return this._toJSONString(tabState);
|
||||
return JSON.stringify(tabState);
|
||||
},
|
||||
|
||||
setTabState: function ssi_setTabState(aTab, aState, aOptions) {
|
||||
@ -1635,8 +1632,8 @@ let SessionStoreInternal = {
|
||||
if (!aTab.ownerDocument.defaultView.__SSi) {
|
||||
throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
if (!aWindow.getBrowser) {
|
||||
throw Components.Exception("Invalid window object: no getBrowser", Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!aWindow.gBrowser) {
|
||||
throw Components.Exception("Invalid window object: no gBrowser", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
// Flush all data queued in the content script because we will need that
|
||||
@ -1674,7 +1671,7 @@ let SessionStoreInternal = {
|
||||
|
||||
getClosedTabData: function ssi_getClosedTabDataAt(aWindow) {
|
||||
if ("__SSi" in aWindow) {
|
||||
return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
|
||||
return JSON.stringify(this._windows[aWindow.__SSi]._closedTabs);
|
||||
}
|
||||
|
||||
if (!DyingWindowCache.has(aWindow)) {
|
||||
@ -1682,7 +1679,7 @@ let SessionStoreInternal = {
|
||||
}
|
||||
|
||||
let data = DyingWindowCache.get(aWindow);
|
||||
return this._toJSONString(data._closedTabs);
|
||||
return JSON.stringify(data._closedTabs);
|
||||
},
|
||||
|
||||
undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
|
||||
@ -1699,7 +1696,7 @@ let SessionStoreInternal = {
|
||||
}
|
||||
|
||||
// fetch the data of closed tab, while removing it from the array
|
||||
let closedTab = closedTabs.splice(aIndex, 1).shift();
|
||||
let [closedTab] = closedTabs.splice(aIndex, 1);
|
||||
let closedTabState = closedTab.state;
|
||||
|
||||
// create a new tab
|
||||
@ -1740,7 +1737,7 @@ let SessionStoreInternal = {
|
||||
},
|
||||
|
||||
getClosedWindowData: function ssi_getClosedWindowData() {
|
||||
return this._toJSONString(this._closedWindows);
|
||||
return JSON.stringify(this._closedWindows);
|
||||
},
|
||||
|
||||
undoCloseWindow: function ssi_undoCloseWindow(aIndex) {
|
||||
@ -2420,7 +2417,7 @@ let SessionStoreInternal = {
|
||||
delete this._windows[aWindow.__SSi].extData;
|
||||
}
|
||||
if (winData.cookies) {
|
||||
this.restoreCookies(winData.cookies);
|
||||
SessionCookies.restore(winData.cookies);
|
||||
}
|
||||
if (winData.extData) {
|
||||
if (!this._windows[aWindow.__SSi].extData) {
|
||||
@ -2604,6 +2601,13 @@ let SessionStoreInternal = {
|
||||
// we collect their data for the first time when saving state.
|
||||
DirtyWindows.add(window);
|
||||
|
||||
// In case we didn't collect/receive data for any tabs yet we'll have to
|
||||
// fill the array with at least empty tabData objects until |_tPos| or
|
||||
// we'll end up with |null| entries.
|
||||
for (let tab of Array.slice(tabbrowser.tabs, 0, tab._tPos)) {
|
||||
this._windows[window.__SSi].tabs.push(TabState.collect(tab));
|
||||
}
|
||||
|
||||
// Update the tab state in case we shut down without being notified.
|
||||
this._windows[window.__SSi].tabs[tab._tPos] = tabData;
|
||||
|
||||
@ -2893,25 +2897,6 @@ let SessionStoreInternal = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores cookies
|
||||
* @param aCookies
|
||||
* Array of cookie objects
|
||||
*/
|
||||
restoreCookies: function ssi_restoreCookies(aCookies) {
|
||||
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
|
||||
var MAX_EXPIRY = Math.pow(2, 62);
|
||||
for (let i = 0; i < aCookies.length; i++) {
|
||||
var cookie = aCookies[i];
|
||||
try {
|
||||
Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
|
||||
cookie.value, !!cookie.secure, !!cookie.httponly, true,
|
||||
"expiry" in cookie ? cookie.expiry : MAX_EXPIRY);
|
||||
}
|
||||
catch (ex) { console.error(ex); } // don't let a single cookie stop recovering
|
||||
}
|
||||
},
|
||||
|
||||
/* ........ Disk Access .............. */
|
||||
|
||||
/**
|
||||
@ -3106,15 +3091,6 @@ let SessionStoreInternal = {
|
||||
return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get nsIURI from string
|
||||
* @param string
|
||||
* @returns nsIURI
|
||||
*/
|
||||
_getURIFromString: function ssi_getURIFromString(aString) {
|
||||
return Services.io.newURI(aString, null, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param aState is a session state
|
||||
* @param aRecentCrashes is the number of consecutive crashes
|
||||
@ -3313,19 +3289,6 @@ let SessionStoreInternal = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a JavaScript object into a JSON string
|
||||
* (see http://www.json.org/ for more information).
|
||||
*
|
||||
* The inverse operation consists of JSON.parse(JSON_string).
|
||||
*
|
||||
* @param aJSObject is the object to be converted
|
||||
* @returns the object's JSON representation
|
||||
*/
|
||||
_toJSONString: function ssi_toJSONString(aJSObject) {
|
||||
return JSON.stringify(aJSObject);
|
||||
},
|
||||
|
||||
_sendRestoreCompletedNotifications: function ssi_sendRestoreCompletedNotifications() {
|
||||
// not all windows restored, yet
|
||||
if (this._restoreCount > 1) {
|
||||
|
@ -362,7 +362,9 @@ function waitForPlayState(player, playState) {
|
||||
let checkPausedAt = Task.async(function*(widget, time) {
|
||||
info("Wait for the next auto-refresh");
|
||||
|
||||
yield waitForPlayState(widget.player, "paused");
|
||||
yield waitForStateCondition(widget.player, state => {
|
||||
return state.playState === "paused" && state.currentTime === time;
|
||||
}, "Waiting for animation to pause at " + time + "ms");
|
||||
|
||||
ok(widget.el.classList.contains("paused"), "The widget is in paused mode");
|
||||
is(widget.player.state.currentTime, time,
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -53,8 +53,6 @@ let DebuggerView = {
|
||||
this.Toolbar.initialize();
|
||||
this.Options.initialize();
|
||||
this.Filtering.initialize();
|
||||
this.FilteredSources.initialize();
|
||||
this.FilteredFunctions.initialize();
|
||||
this.StackFrames.initialize();
|
||||
this.StackFramesClassicList.initialize();
|
||||
this.Sources.initialize();
|
||||
@ -88,8 +86,6 @@ let DebuggerView = {
|
||||
this.Toolbar.destroy();
|
||||
this.Options.destroy();
|
||||
this.Filtering.destroy();
|
||||
this.FilteredSources.destroy();
|
||||
this.FilteredFunctions.destroy();
|
||||
this.StackFrames.destroy();
|
||||
this.StackFramesClassicList.destroy();
|
||||
this.Sources.destroy();
|
||||
@ -649,8 +645,6 @@ let DebuggerView = {
|
||||
dumpn("Handling tab navigation in the DebuggerView");
|
||||
|
||||
this.Filtering.clearSearch();
|
||||
this.FilteredSources.clearView();
|
||||
this.FilteredFunctions.clearView();
|
||||
this.GlobalSearch.clearView();
|
||||
this.StackFrames.empty();
|
||||
this.Sources.empty();
|
||||
@ -672,8 +666,6 @@ let DebuggerView = {
|
||||
Toolbar: null,
|
||||
Options: null,
|
||||
Filtering: null,
|
||||
FilteredSources: null,
|
||||
FilteredFunctions: null,
|
||||
GlobalSearch: null,
|
||||
StackFrames: null,
|
||||
Sources: null,
|
||||
|
@ -25,12 +25,21 @@
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||
<script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="text/javascript" src="debugger/utils.js"/>
|
||||
<script type="text/javascript" src="debugger-controller.js"/>
|
||||
<script type="text/javascript" src="debugger-view.js"/>
|
||||
<script type="text/javascript" src="debugger-toolbar.js"/>
|
||||
<script type="text/javascript" src="debugger-panes.js"/>
|
||||
|
||||
<script type="text/javascript" src="debugger/utils.js"/>
|
||||
<script type="text/javascript" src="debugger/sources-view.js"/>
|
||||
<script type="text/javascript" src="debugger/variable-bubble-view.js"/>
|
||||
<script type="text/javascript" src="debugger/tracer-view.js"/>
|
||||
<script type="text/javascript" src="debugger/watch-expressions-view.js"/>
|
||||
<script type="text/javascript" src="debugger/event-listeners-view.js"/>
|
||||
<script type="text/javascript" src="debugger/global-search-view.js"/>
|
||||
<script type="text/javascript" src="debugger/toolbar-view.js"/>
|
||||
<script type="text/javascript" src="debugger/options-view.js"/>
|
||||
<script type="text/javascript" src="debugger/stack-frames-view.js"/>
|
||||
<script type="text/javascript" src="debugger/stack-frames-classic-view.js"/>
|
||||
<script type="text/javascript" src="debugger/filter-view.js"/>
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
|
||||
<commandset id="debuggerCommands"></commandset>
|
||||
|
@ -17,8 +17,8 @@ function test() {
|
||||
gDebugger = gPanel.panelWin;
|
||||
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
|
||||
|
||||
gDebugger.DebuggerView.FilteredSources._autoSelectFirstItem = false;
|
||||
gDebugger.DebuggerView.FilteredFunctions._autoSelectFirstItem = false;
|
||||
gDebugger.DebuggerView.Filtering.FilteredSources._autoSelectFirstItem = false;
|
||||
gDebugger.DebuggerView.Filtering.FilteredFunctions._autoSelectFirstItem = false;
|
||||
|
||||
waitForSourceShown(gPanel, "-01.js")
|
||||
.then(superGenericFileSearch)
|
||||
|
@ -19,7 +19,7 @@ function test() {
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
gSearchView = gDebugger.DebuggerView.FilteredSources;
|
||||
gSearchView = gDebugger.DebuggerView.Filtering.FilteredSources;
|
||||
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
|
||||
|
||||
waitForSourceShown(gPanel, "-01.js")
|
||||
|
@ -20,7 +20,7 @@ function test() {
|
||||
gDebugger = gPanel.panelWin;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
gSourceUtils = gDebugger.SourceUtils;
|
||||
gSearchView = gDebugger.DebuggerView.FilteredSources;
|
||||
gSearchView = gDebugger.DebuggerView.Filtering.FilteredSources;
|
||||
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
|
||||
|
||||
waitForSourceShown(gPanel, "-01.js")
|
||||
|
@ -18,7 +18,7 @@ function test() {
|
||||
gEditor = gDebugger.DebuggerView.editor;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
|
||||
gFilteredFunctions = gDebugger.DebuggerView.FilteredFunctions;
|
||||
gFilteredFunctions = gDebugger.DebuggerView.Filtering.FilteredFunctions;
|
||||
|
||||
waitForSourceShown(gPanel, "-01.js")
|
||||
.then(() => showSource("doc_function-search.html"))
|
||||
|
@ -727,8 +727,8 @@ function prepareDebugger(aDebugger) {
|
||||
let view = aDebugger.panelWin.DebuggerView;
|
||||
view.Variables.lazyEmpty = false;
|
||||
view.Variables.lazySearch = false;
|
||||
view.FilteredSources._autoSelectFirstItem = true;
|
||||
view.FilteredFunctions._autoSelectFirstItem = true;
|
||||
view.Filtering.FilteredSources._autoSelectFirstItem = true;
|
||||
view.Filtering.FilteredFunctions._autoSelectFirstItem = true;
|
||||
} else {
|
||||
// Nothing to do here yet.
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const utils = {
|
||||
const XULUtils = {
|
||||
/**
|
||||
* Create <command> elements within `commandset` with event handlers
|
||||
* bound to the `command` event
|
||||
@ -26,3 +26,296 @@ const utils = {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Used to detect minification for automatic pretty printing
|
||||
const SAMPLE_SIZE = 50; // no of lines
|
||||
const INDENT_COUNT_THRESHOLD = 5; // percentage
|
||||
const CHARACTER_LIMIT = 250; // line character limit
|
||||
|
||||
/**
|
||||
* Utility functions for handling sources.
|
||||
*/
|
||||
const SourceUtils = {
|
||||
_labelsCache: new Map(), // Can't use WeakMaps because keys are strings.
|
||||
_groupsCache: new Map(),
|
||||
_minifiedCache: new WeakMap(),
|
||||
|
||||
/**
|
||||
* Returns true if the specified url and/or content type are specific to
|
||||
* javascript files.
|
||||
*
|
||||
* @return boolean
|
||||
* True if the source is likely javascript.
|
||||
*/
|
||||
isJavaScript: function(aUrl, aContentType = "") {
|
||||
return (aUrl && /\.jsm?$/.test(this.trimUrlQuery(aUrl))) ||
|
||||
aContentType.contains("javascript");
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if the source text is minified by using
|
||||
* the percentage indented of a subset of lines
|
||||
*
|
||||
* @return object
|
||||
* A promise that resolves to true if source text is minified.
|
||||
*/
|
||||
isMinified: Task.async(function*(sourceClient) {
|
||||
if (this._minifiedCache.has(sourceClient)) {
|
||||
return this._minifiedCache.get(sourceClient);
|
||||
}
|
||||
|
||||
let [, text] = yield DebuggerController.SourceScripts.getText(sourceClient);
|
||||
let isMinified;
|
||||
let lineEndIndex = 0;
|
||||
let lineStartIndex = 0;
|
||||
let lines = 0;
|
||||
let indentCount = 0;
|
||||
let overCharLimit = false;
|
||||
|
||||
// Strip comments.
|
||||
text = text.replace(/\/\*[\S\s]*?\*\/|\/\/(.+|\n)/g, "");
|
||||
|
||||
while (lines++ < SAMPLE_SIZE) {
|
||||
lineEndIndex = text.indexOf("\n", lineStartIndex);
|
||||
if (lineEndIndex == -1) {
|
||||
break;
|
||||
}
|
||||
if (/^\s+/.test(text.slice(lineStartIndex, lineEndIndex))) {
|
||||
indentCount++;
|
||||
}
|
||||
// For files with no indents but are not minified.
|
||||
if ((lineEndIndex - lineStartIndex) > CHARACTER_LIMIT) {
|
||||
overCharLimit = true;
|
||||
break;
|
||||
}
|
||||
lineStartIndex = lineEndIndex + 1;
|
||||
}
|
||||
|
||||
isMinified =
|
||||
((indentCount / lines) * 100) < INDENT_COUNT_THRESHOLD || overCharLimit;
|
||||
|
||||
this._minifiedCache.set(sourceClient, isMinified);
|
||||
return isMinified;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Clears the labels, groups and minify cache, populated by methods like
|
||||
* SourceUtils.getSourceLabel or Source Utils.getSourceGroup.
|
||||
* This should be done every time the content location changes.
|
||||
*/
|
||||
clearCache: function() {
|
||||
this._labelsCache.clear();
|
||||
this._groupsCache.clear();
|
||||
this._minifiedCache.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a unique, simplified label from a source url.
|
||||
*
|
||||
* @param string aUrl
|
||||
* The source url.
|
||||
* @return string
|
||||
* The simplified label.
|
||||
*/
|
||||
getSourceLabel: function(aUrl) {
|
||||
let cachedLabel = this._labelsCache.get(aUrl);
|
||||
if (cachedLabel) {
|
||||
return cachedLabel;
|
||||
}
|
||||
|
||||
let sourceLabel = null;
|
||||
|
||||
for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
|
||||
if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
|
||||
sourceLabel = aUrl.substring(KNOWN_SOURCE_GROUPS[name].length);
|
||||
}
|
||||
}
|
||||
|
||||
if (!sourceLabel) {
|
||||
sourceLabel = this.trimUrl(aUrl);
|
||||
}
|
||||
|
||||
let unicodeLabel = NetworkHelper.convertToUnicode(unescape(sourceLabel));
|
||||
this._labelsCache.set(aUrl, unicodeLabel);
|
||||
return unicodeLabel;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets as much information as possible about the hostname and directory paths
|
||||
* of an url to create a short url group identifier.
|
||||
*
|
||||
* @param string aUrl
|
||||
* The source url.
|
||||
* @return string
|
||||
* The simplified group.
|
||||
*/
|
||||
getSourceGroup: function(aUrl) {
|
||||
let cachedGroup = this._groupsCache.get(aUrl);
|
||||
if (cachedGroup) {
|
||||
return cachedGroup;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use an nsIURL to parse all the url path parts.
|
||||
var uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
|
||||
} catch (e) {
|
||||
// This doesn't look like a url, or nsIURL can't handle it.
|
||||
return "";
|
||||
}
|
||||
|
||||
let groupLabel = uri.prePath;
|
||||
|
||||
for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
|
||||
if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
|
||||
groupLabel = name;
|
||||
}
|
||||
}
|
||||
|
||||
let unicodeLabel = NetworkHelper.convertToUnicode(unescape(groupLabel));
|
||||
this._groupsCache.set(aUrl, unicodeLabel)
|
||||
return unicodeLabel;
|
||||
},
|
||||
|
||||
/**
|
||||
* Trims the url by shortening it if it exceeds a certain length, adding an
|
||||
* ellipsis at the end.
|
||||
*
|
||||
* @param string aUrl
|
||||
* The source url.
|
||||
* @param number aLength [optional]
|
||||
* The expected source url length.
|
||||
* @param number aSection [optional]
|
||||
* The section to trim. Supported values: "start", "center", "end"
|
||||
* @return string
|
||||
* The shortened url.
|
||||
*/
|
||||
trimUrlLength: function(aUrl, aLength, aSection) {
|
||||
aLength = aLength || SOURCE_URL_DEFAULT_MAX_LENGTH;
|
||||
aSection = aSection || "end";
|
||||
|
||||
if (aUrl.length > aLength) {
|
||||
switch (aSection) {
|
||||
case "start":
|
||||
return L10N.ellipsis + aUrl.slice(-aLength);
|
||||
break;
|
||||
case "center":
|
||||
return aUrl.substr(0, aLength / 2 - 1) + L10N.ellipsis + aUrl.slice(-aLength / 2 + 1);
|
||||
break;
|
||||
case "end":
|
||||
return aUrl.substr(0, aLength) + L10N.ellipsis;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return aUrl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Trims the query part or reference identifier of a url string, if necessary.
|
||||
*
|
||||
* @param string aUrl
|
||||
* The source url.
|
||||
* @return string
|
||||
* The shortened url.
|
||||
*/
|
||||
trimUrlQuery: function(aUrl) {
|
||||
let length = aUrl.length;
|
||||
let q1 = aUrl.indexOf('?');
|
||||
let q2 = aUrl.indexOf('&');
|
||||
let q3 = aUrl.indexOf('#');
|
||||
let q = Math.min(q1 != -1 ? q1 : length,
|
||||
q2 != -1 ? q2 : length,
|
||||
q3 != -1 ? q3 : length);
|
||||
|
||||
return aUrl.slice(0, q);
|
||||
},
|
||||
|
||||
/**
|
||||
* Trims as much as possible from a url, while keeping the label unique
|
||||
* in the sources container.
|
||||
*
|
||||
* @param string | nsIURL aUrl
|
||||
* The source url.
|
||||
* @param string aLabel [optional]
|
||||
* The resulting label at each step.
|
||||
* @param number aSeq [optional]
|
||||
* The current iteration step.
|
||||
* @return string
|
||||
* The resulting label at the final step.
|
||||
*/
|
||||
trimUrl: function(aUrl, aLabel, aSeq) {
|
||||
if (!(aUrl instanceof Ci.nsIURL)) {
|
||||
try {
|
||||
// Use an nsIURL to parse all the url path parts.
|
||||
aUrl = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
|
||||
} catch (e) {
|
||||
// This doesn't look like a url, or nsIURL can't handle it.
|
||||
return aUrl;
|
||||
}
|
||||
}
|
||||
if (!aSeq) {
|
||||
let name = aUrl.fileName;
|
||||
if (name) {
|
||||
// This is a regular file url, get only the file name (contains the
|
||||
// base name and extension if available).
|
||||
|
||||
// If this url contains an invalid query, unfortunately nsIURL thinks
|
||||
// it's part of the file extension. It must be removed.
|
||||
aLabel = aUrl.fileName.replace(/\&.*/, "");
|
||||
} else {
|
||||
// This is not a file url, hence there is no base name, nor extension.
|
||||
// Proceed using other available information.
|
||||
aLabel = "";
|
||||
}
|
||||
aSeq = 1;
|
||||
}
|
||||
|
||||
// If we have a label and it doesn't only contain a query...
|
||||
if (aLabel && aLabel.indexOf("?") != 0) {
|
||||
// A page may contain multiple requests to the same url but with different
|
||||
// queries. It is *not* redundant to show each one.
|
||||
if (!DebuggerView.Sources.getItemForAttachment(e => e.label == aLabel)) {
|
||||
return aLabel;
|
||||
}
|
||||
}
|
||||
|
||||
// Append the url query.
|
||||
if (aSeq == 1) {
|
||||
let query = aUrl.query;
|
||||
if (query) {
|
||||
return this.trimUrl(aUrl, aLabel + "?" + query, aSeq + 1);
|
||||
}
|
||||
aSeq++;
|
||||
}
|
||||
// Append the url reference.
|
||||
if (aSeq == 2) {
|
||||
let ref = aUrl.ref;
|
||||
if (ref) {
|
||||
return this.trimUrl(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1);
|
||||
}
|
||||
aSeq++;
|
||||
}
|
||||
// Prepend the url directory.
|
||||
if (aSeq == 3) {
|
||||
let dir = aUrl.directory;
|
||||
if (dir) {
|
||||
return this.trimUrl(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1);
|
||||
}
|
||||
aSeq++;
|
||||
}
|
||||
// Prepend the hostname and port number.
|
||||
if (aSeq == 4) {
|
||||
let host = aUrl.hostPort;
|
||||
if (host) {
|
||||
return this.trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
|
||||
}
|
||||
aSeq++;
|
||||
}
|
||||
// Use the whole url spec but ignoring the reference.
|
||||
if (aSeq == 5) {
|
||||
return this.trimUrl(aUrl, aUrl.specIgnoringRef, aSeq + 1);
|
||||
}
|
||||
// Give up.
|
||||
return aUrl.spec;
|
||||
}
|
||||
};
|
||||
|
||||
|
274
browser/devtools/debugger/views/event-listeners-view.js
Normal file
274
browser/devtools/debugger/views/event-listeners-view.js
Normal file
@ -0,0 +1,274 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Functions handling the event listeners UI.
|
||||
*/
|
||||
function EventListenersView(DebuggerController) {
|
||||
dumpn("EventListenersView was instantiated");
|
||||
|
||||
this.Breakpoints = DebuggerController.Breakpoints;
|
||||
|
||||
this._onCheck = this._onCheck.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
EventListenersView.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the EventListenersView");
|
||||
|
||||
this.widget = new SideMenuWidget(document.getElementById("event-listeners"), {
|
||||
showItemCheckboxes: true,
|
||||
showGroupCheckboxes: true
|
||||
});
|
||||
|
||||
this.emptyText = L10N.getStr("noEventListenersText");
|
||||
this._eventCheckboxTooltip = L10N.getStr("eventCheckboxTooltip");
|
||||
this._onSelectorString = " " + L10N.getStr("eventOnSelector") + " ";
|
||||
this._inSourceString = " " + L10N.getStr("eventInSource") + " ";
|
||||
this._inNativeCodeString = L10N.getStr("eventNative");
|
||||
|
||||
this.widget.addEventListener("check", this._onCheck, false);
|
||||
this.widget.addEventListener("click", this._onClick, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the EventListenersView");
|
||||
|
||||
this.widget.removeEventListener("check", this._onCheck, false);
|
||||
this.widget.removeEventListener("click", this._onClick, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an event to this event listeners container.
|
||||
*
|
||||
* @param object aListener
|
||||
* The listener object coming from the active thread.
|
||||
* @param object aOptions [optional]
|
||||
* Additional options for adding the source. Supported options:
|
||||
* - staged: true to stage the item to be appended later
|
||||
*/
|
||||
addListener: function(aListener, aOptions = {}) {
|
||||
let { node: { selector }, function: { url }, type } = aListener;
|
||||
if (!type) return;
|
||||
|
||||
// Some listener objects may be added from plugins, thus getting
|
||||
// translated to native code.
|
||||
if (!url) {
|
||||
url = this._inNativeCodeString;
|
||||
}
|
||||
|
||||
// If an event item for this listener's url and type was already added,
|
||||
// avoid polluting the view and simply increase the "targets" count.
|
||||
let eventItem = this.getItemForPredicate(aItem =>
|
||||
aItem.attachment.url == url &&
|
||||
aItem.attachment.type == type);
|
||||
|
||||
if (eventItem) {
|
||||
let { selectors, view: { targets } } = eventItem.attachment;
|
||||
if (selectors.indexOf(selector) == -1) {
|
||||
selectors.push(selector);
|
||||
targets.setAttribute("value", L10N.getFormatStr("eventNodes", selectors.length));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// There's no easy way of grouping event types into higher-level groups,
|
||||
// so we need to do this by hand.
|
||||
let is = (...args) => args.indexOf(type) != -1;
|
||||
let has = str => type.contains(str);
|
||||
let starts = str => type.startsWith(str);
|
||||
let group;
|
||||
|
||||
if (starts("animation")) {
|
||||
group = L10N.getStr("animationEvents");
|
||||
} else if (starts("audio")) {
|
||||
group = L10N.getStr("audioEvents");
|
||||
} else if (is("levelchange")) {
|
||||
group = L10N.getStr("batteryEvents");
|
||||
} else if (is("cut", "copy", "paste")) {
|
||||
group = L10N.getStr("clipboardEvents");
|
||||
} else if (starts("composition")) {
|
||||
group = L10N.getStr("compositionEvents");
|
||||
} else if (starts("device")) {
|
||||
group = L10N.getStr("deviceEvents");
|
||||
} else if (is("fullscreenchange", "fullscreenerror", "orientationchange",
|
||||
"overflow", "resize", "scroll", "underflow", "zoom")) {
|
||||
group = L10N.getStr("displayEvents");
|
||||
} else if (starts("drag") || starts("drop")) {
|
||||
group = L10N.getStr("dragAndDropEvents");
|
||||
} else if (starts("gamepad")) {
|
||||
group = L10N.getStr("gamepadEvents");
|
||||
} else if (is("canplay", "canplaythrough", "durationchange", "emptied",
|
||||
"ended", "loadeddata", "loadedmetadata", "pause", "play", "playing",
|
||||
"ratechange", "seeked", "seeking", "stalled", "suspend", "timeupdate",
|
||||
"volumechange", "waiting")) {
|
||||
group = L10N.getStr("mediaEvents");
|
||||
} else if (is("blocked", "complete", "success", "upgradeneeded", "versionchange")) {
|
||||
group = L10N.getStr("indexedDBEvents");
|
||||
} else if (is("blur", "change", "focus", "focusin", "focusout", "invalid",
|
||||
"reset", "select", "submit")) {
|
||||
group = L10N.getStr("interactionEvents");
|
||||
} else if (starts("key") || is("input")) {
|
||||
group = L10N.getStr("keyboardEvents");
|
||||
} else if (starts("mouse") || has("click") || is("contextmenu", "show", "wheel")) {
|
||||
group = L10N.getStr("mouseEvents");
|
||||
} else if (starts("DOM")) {
|
||||
group = L10N.getStr("mutationEvents");
|
||||
} else if (is("abort", "error", "hashchange", "load", "loadend", "loadstart",
|
||||
"pagehide", "pageshow", "progress", "timeout", "unload", "uploadprogress",
|
||||
"visibilitychange")) {
|
||||
group = L10N.getStr("navigationEvents");
|
||||
} else if (is("pointerlockchange", "pointerlockerror")) {
|
||||
group = L10N.getStr("pointerLockEvents");
|
||||
} else if (is("compassneedscalibration", "userproximity")) {
|
||||
group = L10N.getStr("sensorEvents");
|
||||
} else if (starts("storage")) {
|
||||
group = L10N.getStr("storageEvents");
|
||||
} else if (is("beginEvent", "endEvent", "repeatEvent")) {
|
||||
group = L10N.getStr("timeEvents");
|
||||
} else if (starts("touch")) {
|
||||
group = L10N.getStr("touchEvents");
|
||||
} else {
|
||||
group = L10N.getStr("otherEvents");
|
||||
}
|
||||
|
||||
// Create the element node for the event listener item.
|
||||
let itemView = this._createItemView(type, selector, url);
|
||||
|
||||
// Event breakpoints survive target navigations. Make sure the newly
|
||||
// inserted event item is correctly checked.
|
||||
let checkboxState =
|
||||
this.Breakpoints.DOM.activeEventNames.indexOf(type) != -1;
|
||||
|
||||
// Append an event listener item to this container.
|
||||
this.push([itemView.container], {
|
||||
staged: aOptions.staged, /* stage the item to be appended later? */
|
||||
attachment: {
|
||||
url: url,
|
||||
type: type,
|
||||
view: itemView,
|
||||
selectors: [selector],
|
||||
group: group,
|
||||
checkboxState: checkboxState,
|
||||
checkboxTooltip: this._eventCheckboxTooltip
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets all the event types known to this container.
|
||||
*
|
||||
* @return array
|
||||
* List of event types, for example ["load", "click"...]
|
||||
*/
|
||||
getAllEvents: function() {
|
||||
return this.attachments.map(e => e.type);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the checked event types in this container.
|
||||
*
|
||||
* @return array
|
||||
* List of event types, for example ["load", "click"...]
|
||||
*/
|
||||
getCheckedEvents: function() {
|
||||
return this.attachments.filter(e => e.checkboxState).map(e => e.type);
|
||||
},
|
||||
|
||||
/**
|
||||
* Customization function for creating an item's UI.
|
||||
*
|
||||
* @param string aType
|
||||
* The event type, for example "click".
|
||||
* @param string aSelector
|
||||
* The target element's selector.
|
||||
* @param string url
|
||||
* The source url in which the event listener is located.
|
||||
* @return object
|
||||
* An object containing the event listener view nodes.
|
||||
*/
|
||||
_createItemView: function(aType, aSelector, aUrl) {
|
||||
let container = document.createElement("hbox");
|
||||
container.className = "dbg-event-listener";
|
||||
|
||||
let eventType = document.createElement("label");
|
||||
eventType.className = "plain dbg-event-listener-type";
|
||||
eventType.setAttribute("value", aType);
|
||||
container.appendChild(eventType);
|
||||
|
||||
let typeSeparator = document.createElement("label");
|
||||
typeSeparator.className = "plain dbg-event-listener-separator";
|
||||
typeSeparator.setAttribute("value", this._onSelectorString);
|
||||
container.appendChild(typeSeparator);
|
||||
|
||||
let eventTargets = document.createElement("label");
|
||||
eventTargets.className = "plain dbg-event-listener-targets";
|
||||
eventTargets.setAttribute("value", aSelector);
|
||||
container.appendChild(eventTargets);
|
||||
|
||||
let selectorSeparator = document.createElement("label");
|
||||
selectorSeparator.className = "plain dbg-event-listener-separator";
|
||||
selectorSeparator.setAttribute("value", this._inSourceString);
|
||||
container.appendChild(selectorSeparator);
|
||||
|
||||
let eventLocation = document.createElement("label");
|
||||
eventLocation.className = "plain dbg-event-listener-location";
|
||||
eventLocation.setAttribute("value", SourceUtils.getSourceLabel(aUrl));
|
||||
eventLocation.setAttribute("flex", "1");
|
||||
eventLocation.setAttribute("crop", "center");
|
||||
container.appendChild(eventLocation);
|
||||
|
||||
return {
|
||||
container: container,
|
||||
type: eventType,
|
||||
targets: eventTargets,
|
||||
location: eventLocation
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* The check listener for the event listeners container.
|
||||
*/
|
||||
_onCheck: function({ detail: { description, checked }, target }) {
|
||||
if (description == "item") {
|
||||
this.getItemForElement(target).attachment.checkboxState = checked;
|
||||
this.Breakpoints.DOM.scheduleEventBreakpointsUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check all the event items in this group.
|
||||
this.items
|
||||
.filter(e => e.attachment.group == description)
|
||||
.forEach(e => this.callMethod("checkItem", e.target, checked));
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for the event listeners container.
|
||||
*/
|
||||
_onClick: function({ target }) {
|
||||
// Changing the checkbox state is handled by the _onCheck event. Avoid
|
||||
// handling that again in this click event, so pass in "noSiblings"
|
||||
// when retrieving the target's item, to ignore the checkbox.
|
||||
let eventItem = this.getItemForElement(target, { noSiblings: true });
|
||||
if (eventItem) {
|
||||
let newState = eventItem.attachment.checkboxState ^= 1;
|
||||
this.callMethod("checkItem", eventItem.target, newState);
|
||||
}
|
||||
},
|
||||
|
||||
_eventCheckboxTooltip: "",
|
||||
_onSelectorString: "",
|
||||
_inSourceString: "",
|
||||
_inNativeCodeString: ""
|
||||
});
|
||||
|
||||
DebuggerView.EventListeners = new EventListenersView(DebuggerController);
|
912
browser/devtools/debugger/views/filter-view.js
Normal file
912
browser/devtools/debugger/views/filter-view.js
Normal file
@ -0,0 +1,912 @@
|
||||
/* 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";
|
||||
|
||||
|
||||
/**
|
||||
* Functions handling the filtering UI.
|
||||
*/
|
||||
function FilterView(DebuggerController, DebuggerView) {
|
||||
dumpn("FilterView was instantiated");
|
||||
|
||||
this.Parser = DebuggerController.Parser;
|
||||
|
||||
this.DebuggerView = DebuggerView;
|
||||
this.FilteredSources = new FilteredSourcesView(DebuggerView);
|
||||
this.FilteredFunctions = new FilteredFunctionsView(DebuggerController.SourceScripts,
|
||||
DebuggerController.Parser,
|
||||
DebuggerView);
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onInput = this._onInput.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._onBlur = this._onBlur.bind(this);
|
||||
}
|
||||
|
||||
FilterView.prototype = {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the FilterView");
|
||||
|
||||
this._searchbox = document.getElementById("searchbox");
|
||||
this._searchboxHelpPanel = document.getElementById("searchbox-help-panel");
|
||||
this._filterLabel = document.getElementById("filter-label");
|
||||
this._globalOperatorButton = document.getElementById("global-operator-button");
|
||||
this._globalOperatorLabel = document.getElementById("global-operator-label");
|
||||
this._functionOperatorButton = document.getElementById("function-operator-button");
|
||||
this._functionOperatorLabel = document.getElementById("function-operator-label");
|
||||
this._tokenOperatorButton = document.getElementById("token-operator-button");
|
||||
this._tokenOperatorLabel = document.getElementById("token-operator-label");
|
||||
this._lineOperatorButton = document.getElementById("line-operator-button");
|
||||
this._lineOperatorLabel = document.getElementById("line-operator-label");
|
||||
this._variableOperatorButton = document.getElementById("variable-operator-button");
|
||||
this._variableOperatorLabel = document.getElementById("variable-operator-label");
|
||||
|
||||
this._fileSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("fileSearchKey"));
|
||||
this._globalSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("globalSearchKey"));
|
||||
this._filteredFunctionsKey = ShortcutUtils.prettifyShortcut(document.getElementById("functionSearchKey"));
|
||||
this._tokenSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("tokenSearchKey"));
|
||||
this._lineSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("lineSearchKey"));
|
||||
this._variableSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("variableSearchKey"));
|
||||
|
||||
this._searchbox.addEventListener("click", this._onClick, false);
|
||||
this._searchbox.addEventListener("select", this._onInput, false);
|
||||
this._searchbox.addEventListener("input", this._onInput, false);
|
||||
this._searchbox.addEventListener("keypress", this._onKeyPress, false);
|
||||
this._searchbox.addEventListener("blur", this._onBlur, false);
|
||||
|
||||
let placeholder = L10N.getFormatStr("emptySearchText", this._fileSearchKey);
|
||||
this._searchbox.setAttribute("placeholder", placeholder);
|
||||
|
||||
this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG);
|
||||
this._functionOperatorButton.setAttribute("label", SEARCH_FUNCTION_FLAG);
|
||||
this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG);
|
||||
this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG);
|
||||
this._variableOperatorButton.setAttribute("label", SEARCH_VARIABLE_FLAG);
|
||||
|
||||
this._filterLabel.setAttribute("value",
|
||||
L10N.getFormatStr("searchPanelFilter", this._fileSearchKey));
|
||||
this._globalOperatorLabel.setAttribute("value",
|
||||
L10N.getFormatStr("searchPanelGlobal", this._globalSearchKey));
|
||||
this._functionOperatorLabel.setAttribute("value",
|
||||
L10N.getFormatStr("searchPanelFunction", this._filteredFunctionsKey));
|
||||
this._tokenOperatorLabel.setAttribute("value",
|
||||
L10N.getFormatStr("searchPanelToken", this._tokenSearchKey));
|
||||
this._lineOperatorLabel.setAttribute("value",
|
||||
L10N.getFormatStr("searchPanelGoToLine", this._lineSearchKey));
|
||||
this._variableOperatorLabel.setAttribute("value",
|
||||
L10N.getFormatStr("searchPanelVariable", this._variableSearchKey));
|
||||
|
||||
this.FilteredSources.initialize();
|
||||
this.FilteredFunctions.initialize();
|
||||
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the FilterView");
|
||||
|
||||
this._searchbox.removeEventListener("click", this._onClick, false);
|
||||
this._searchbox.removeEventListener("select", this._onInput, false);
|
||||
this._searchbox.removeEventListener("input", this._onInput, false);
|
||||
this._searchbox.removeEventListener("keypress", this._onKeyPress, false);
|
||||
this._searchbox.removeEventListener("blur", this._onBlur, false);
|
||||
|
||||
this.FilteredSources.destroy();
|
||||
this.FilteredFunctions.destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
XULUtils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
fileSearchCommand: () => this._doFileSearch(),
|
||||
globalSearchCommand: () => this._doGlobalSearch(),
|
||||
functionSearchCommand: () => this._doFunctionSearch(),
|
||||
tokenSearchCommand: () => this._doTokenSearch(),
|
||||
lineSearchCommand: () => this._doLineSearch(),
|
||||
variableSearchCommand: () => this._doVariableSearch(),
|
||||
variablesFocusCommand: () => this._doVariablesFocus()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the entered operator and arguments in the searchbox.
|
||||
* @return array
|
||||
*/
|
||||
get searchData() {
|
||||
let operator = "", args = [];
|
||||
|
||||
let rawValue = this._searchbox.value;
|
||||
let rawLength = rawValue.length;
|
||||
let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG);
|
||||
let functionFlagIndex = rawValue.indexOf(SEARCH_FUNCTION_FLAG);
|
||||
let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG);
|
||||
let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
|
||||
let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG);
|
||||
|
||||
// This is not a global, function or variable search, allow file/line flags.
|
||||
if (globalFlagIndex != 0 && functionFlagIndex != 0 && variableFlagIndex != 0) {
|
||||
// Token search has precedence over line search.
|
||||
if (tokenFlagIndex != -1) {
|
||||
operator = SEARCH_TOKEN_FLAG;
|
||||
args.push(rawValue.slice(0, tokenFlagIndex)); // file
|
||||
args.push(rawValue.substr(tokenFlagIndex + 1, rawLength)); // token
|
||||
} else if (lineFlagIndex != -1) {
|
||||
operator = SEARCH_LINE_FLAG;
|
||||
args.push(rawValue.slice(0, lineFlagIndex)); // file
|
||||
args.push(+rawValue.substr(lineFlagIndex + 1, rawLength) || 0); // line
|
||||
} else {
|
||||
args.push(rawValue);
|
||||
}
|
||||
}
|
||||
// Global searches dissalow the use of file or line flags.
|
||||
else if (globalFlagIndex == 0) {
|
||||
operator = SEARCH_GLOBAL_FLAG;
|
||||
args.push(rawValue.slice(1));
|
||||
}
|
||||
// Function searches dissalow the use of file or line flags.
|
||||
else if (functionFlagIndex == 0) {
|
||||
operator = SEARCH_FUNCTION_FLAG;
|
||||
args.push(rawValue.slice(1));
|
||||
}
|
||||
// Variable searches dissalow the use of file or line flags.
|
||||
else if (variableFlagIndex == 0) {
|
||||
operator = SEARCH_VARIABLE_FLAG;
|
||||
args.push(rawValue.slice(1));
|
||||
}
|
||||
|
||||
return [operator, args];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current search operator.
|
||||
* @return string
|
||||
*/
|
||||
get searchOperator() this.searchData[0],
|
||||
|
||||
/**
|
||||
* Returns the current search arguments.
|
||||
* @return array
|
||||
*/
|
||||
get searchArguments() this.searchData[1],
|
||||
|
||||
/**
|
||||
* Clears the text from the searchbox and any changed views.
|
||||
*/
|
||||
clearSearch: function() {
|
||||
this._searchbox.value = "";
|
||||
this.clearViews();
|
||||
|
||||
this.FilteredSources.clearView();
|
||||
this.FilteredFunctions.clearView();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears all the views that may pop up when searching.
|
||||
*/
|
||||
clearViews: function() {
|
||||
this.DebuggerView.GlobalSearch.clearView();
|
||||
this.FilteredSources.clearView();
|
||||
this.FilteredFunctions.clearView();
|
||||
this._searchboxHelpPanel.hidePopup();
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a line search if necessary.
|
||||
* (Jump to lines in the currently visible source).
|
||||
*
|
||||
* @param number aLine
|
||||
* The source line number to jump to.
|
||||
*/
|
||||
_performLineSearch: function(aLine) {
|
||||
// Make sure we're actually searching for a valid line.
|
||||
if (aLine) {
|
||||
this.DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 }, "center");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a token search if necessary.
|
||||
* (Search for tokens in the currently visible source).
|
||||
*
|
||||
* @param string aToken
|
||||
* The source token to find.
|
||||
*/
|
||||
_performTokenSearch: function(aToken) {
|
||||
// Make sure we're actually searching for a valid token.
|
||||
if (!aToken) {
|
||||
return;
|
||||
}
|
||||
this.DebuggerView.editor.find(aToken);
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the search container.
|
||||
*/
|
||||
_onClick: function() {
|
||||
// If there's some text in the searchbox, displaying a panel would
|
||||
// interfere with double/triple click default behaviors.
|
||||
if (!this._searchbox.value) {
|
||||
this._searchboxHelpPanel.openPopup(this._searchbox);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The input listener for the search container.
|
||||
*/
|
||||
_onInput: function() {
|
||||
this.clearViews();
|
||||
|
||||
// Make sure we're actually searching for something.
|
||||
if (!this._searchbox.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform the required search based on the specified operator.
|
||||
switch (this.searchOperator) {
|
||||
case SEARCH_GLOBAL_FLAG:
|
||||
// Schedule a global search for when the user stops typing.
|
||||
this.DebuggerView.GlobalSearch.scheduleSearch(this.searchArguments[0]);
|
||||
break;
|
||||
case SEARCH_FUNCTION_FLAG:
|
||||
// Schedule a function search for when the user stops typing.
|
||||
this.FilteredFunctions.scheduleSearch(this.searchArguments[0]);
|
||||
break;
|
||||
case SEARCH_VARIABLE_FLAG:
|
||||
// Schedule a variable search for when the user stops typing.
|
||||
this.DebuggerView.Variables.scheduleSearch(this.searchArguments[0]);
|
||||
break;
|
||||
case SEARCH_TOKEN_FLAG:
|
||||
// Schedule a file+token search for when the user stops typing.
|
||||
this.FilteredSources.scheduleSearch(this.searchArguments[0]);
|
||||
this._performTokenSearch(this.searchArguments[1]);
|
||||
break;
|
||||
case SEARCH_LINE_FLAG:
|
||||
// Schedule a file+line search for when the user stops typing.
|
||||
this.FilteredSources.scheduleSearch(this.searchArguments[0]);
|
||||
this._performLineSearch(this.searchArguments[1]);
|
||||
break;
|
||||
default:
|
||||
// Schedule a file only search for when the user stops typing.
|
||||
this.FilteredSources.scheduleSearch(this.searchArguments[0]);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The key press listener for the search container.
|
||||
*/
|
||||
_onKeyPress: function(e) {
|
||||
// This attribute is not implemented in Gecko at this time, see bug 680830.
|
||||
e.char = String.fromCharCode(e.charCode);
|
||||
|
||||
// Perform the required action based on the specified operator.
|
||||
let [operator, args] = this.searchData;
|
||||
let isGlobalSearch = operator == SEARCH_GLOBAL_FLAG;
|
||||
let isFunctionSearch = operator == SEARCH_FUNCTION_FLAG;
|
||||
let isVariableSearch = operator == SEARCH_VARIABLE_FLAG;
|
||||
let isTokenSearch = operator == SEARCH_TOKEN_FLAG;
|
||||
let isLineSearch = operator == SEARCH_LINE_FLAG;
|
||||
let isFileOnlySearch = !operator && args.length == 1;
|
||||
|
||||
// Depending on the pressed keys, determine to correct action to perform.
|
||||
let actionToPerform;
|
||||
|
||||
// Meta+G and Ctrl+N focus next matches.
|
||||
if ((e.char == "g" && e.metaKey) || e.char == "n" && e.ctrlKey) {
|
||||
actionToPerform = "selectNext";
|
||||
}
|
||||
// Meta+Shift+G and Ctrl+P focus previous matches.
|
||||
else if ((e.char == "G" && e.metaKey) || e.char == "p" && e.ctrlKey) {
|
||||
actionToPerform = "selectPrev";
|
||||
}
|
||||
// Return, enter, down and up keys focus next or previous matches, while
|
||||
// the escape key switches focus from the search container.
|
||||
else switch (e.keyCode) {
|
||||
case e.DOM_VK_RETURN:
|
||||
var isReturnKey = true;
|
||||
// If the shift key is pressed, focus on the previous result
|
||||
actionToPerform = e.shiftKey ? "selectPrev" : "selectNext";
|
||||
break;
|
||||
case e.DOM_VK_DOWN:
|
||||
actionToPerform = "selectNext";
|
||||
break;
|
||||
case e.DOM_VK_UP:
|
||||
actionToPerform = "selectPrev";
|
||||
break;
|
||||
}
|
||||
|
||||
// If there's no action to perform, or no operator, file line or token
|
||||
// were specified, then this is either a broken or empty search.
|
||||
if (!actionToPerform || (!operator && !args.length)) {
|
||||
this.DebuggerView.editor.dropSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Jump to the next/previous entry in the global search, or perform
|
||||
// a new global search immediately
|
||||
if (isGlobalSearch) {
|
||||
let targetView = this.DebuggerView.GlobalSearch;
|
||||
if (!isReturnKey) {
|
||||
targetView[actionToPerform]();
|
||||
} else if (targetView.hidden) {
|
||||
targetView.scheduleSearch(args[0], 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Jump to the next/previous entry in the function search, perform
|
||||
// a new function search immediately, or clear it.
|
||||
if (isFunctionSearch) {
|
||||
let targetView = this.FilteredFunctions;
|
||||
if (!isReturnKey) {
|
||||
targetView[actionToPerform]();
|
||||
} else if (targetView.hidden) {
|
||||
targetView.scheduleSearch(args[0], 0);
|
||||
} else {
|
||||
if (!targetView.selectedItem) {
|
||||
targetView.selectedIndex = 0;
|
||||
}
|
||||
this.clearSearch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform a new variable search immediately.
|
||||
if (isVariableSearch) {
|
||||
let targetView = this.DebuggerView.Variables;
|
||||
if (isReturnKey) {
|
||||
targetView.scheduleSearch(args[0], 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Jump to the next/previous entry in the file search, perform
|
||||
// a new file search immediately, or clear it.
|
||||
if (isFileOnlySearch) {
|
||||
let targetView = this.FilteredSources;
|
||||
if (!isReturnKey) {
|
||||
targetView[actionToPerform]();
|
||||
} else if (targetView.hidden) {
|
||||
targetView.scheduleSearch(args[0], 0);
|
||||
} else {
|
||||
if (!targetView.selectedItem) {
|
||||
targetView.selectedIndex = 0;
|
||||
}
|
||||
this.clearSearch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Jump to the next/previous instance of the currently searched token.
|
||||
if (isTokenSearch) {
|
||||
let methods = { selectNext: "findNext", selectPrev: "findPrev" };
|
||||
this.DebuggerView.editor[methods[actionToPerform]]();
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment/decrement the currently searched caret line.
|
||||
if (isLineSearch) {
|
||||
let [, line] = args;
|
||||
let amounts = { selectNext: 1, selectPrev: -1 };
|
||||
|
||||
// Modify the line number and jump to it.
|
||||
line += !isReturnKey ? amounts[actionToPerform] : 0;
|
||||
let lineCount = this.DebuggerView.editor.lineCount();
|
||||
let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line;
|
||||
this._doSearch(SEARCH_LINE_FLAG, lineTarget);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The blur listener for the search container.
|
||||
*/
|
||||
_onBlur: function() {
|
||||
this.clearViews();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a filtering key sequence was pressed.
|
||||
*
|
||||
* @param string aOperator
|
||||
* The operator to use for filtering.
|
||||
*/
|
||||
_doSearch: function(aOperator = "", aText = "") {
|
||||
this._searchbox.focus();
|
||||
this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738.
|
||||
|
||||
if (aText) {
|
||||
this._searchbox.value = aOperator + aText;
|
||||
return;
|
||||
}
|
||||
if (this.DebuggerView.editor.somethingSelected()) {
|
||||
this._searchbox.value = aOperator + this.DebuggerView.editor.getSelection();
|
||||
return;
|
||||
}
|
||||
if (SEARCH_AUTOFILL.indexOf(aOperator) != -1) {
|
||||
let cursor = this.DebuggerView.editor.getCursor();
|
||||
let content = this.DebuggerView.editor.getText();
|
||||
let location = this.DebuggerView.Sources.selectedItem.attachment.source.url;
|
||||
let source = this.Parser.get(content, location);
|
||||
let identifier = source.getIdentifierAt({ line: cursor.line+1, column: cursor.ch });
|
||||
|
||||
if (identifier && identifier.name) {
|
||||
this._searchbox.value = aOperator + identifier.name;
|
||||
this._searchbox.select();
|
||||
this._searchbox.selectionStart += aOperator.length;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._searchbox.value = aOperator;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the source location filter key sequence was pressed.
|
||||
*/
|
||||
_doFileSearch: function() {
|
||||
this._doSearch();
|
||||
this._searchboxHelpPanel.openPopup(this._searchbox);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the global search filter key sequence was pressed.
|
||||
*/
|
||||
_doGlobalSearch: function() {
|
||||
this._doSearch(SEARCH_GLOBAL_FLAG);
|
||||
this._searchboxHelpPanel.hidePopup();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the source function filter key sequence was pressed.
|
||||
*/
|
||||
_doFunctionSearch: function() {
|
||||
this._doSearch(SEARCH_FUNCTION_FLAG);
|
||||
this._searchboxHelpPanel.hidePopup();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the source token filter key sequence was pressed.
|
||||
*/
|
||||
_doTokenSearch: function() {
|
||||
this._doSearch(SEARCH_TOKEN_FLAG);
|
||||
this._searchboxHelpPanel.hidePopup();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the source line filter key sequence was pressed.
|
||||
*/
|
||||
_doLineSearch: function() {
|
||||
this._doSearch(SEARCH_LINE_FLAG);
|
||||
this._searchboxHelpPanel.hidePopup();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the variable search filter key sequence was pressed.
|
||||
*/
|
||||
_doVariableSearch: function() {
|
||||
this._doSearch(SEARCH_VARIABLE_FLAG);
|
||||
this._searchboxHelpPanel.hidePopup();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the variables focus key sequence was pressed.
|
||||
*/
|
||||
_doVariablesFocus: function() {
|
||||
this.DebuggerView.showInstrumentsPane();
|
||||
this.DebuggerView.Variables.focusFirstVisibleItem();
|
||||
},
|
||||
|
||||
_searchbox: null,
|
||||
_searchboxHelpPanel: null,
|
||||
_globalOperatorButton: null,
|
||||
_globalOperatorLabel: null,
|
||||
_functionOperatorButton: null,
|
||||
_functionOperatorLabel: null,
|
||||
_tokenOperatorButton: null,
|
||||
_tokenOperatorLabel: null,
|
||||
_lineOperatorButton: null,
|
||||
_lineOperatorLabel: null,
|
||||
_variableOperatorButton: null,
|
||||
_variableOperatorLabel: null,
|
||||
_fileSearchKey: "",
|
||||
_globalSearchKey: "",
|
||||
_filteredFunctionsKey: "",
|
||||
_tokenSearchKey: "",
|
||||
_lineSearchKey: "",
|
||||
_variableSearchKey: "",
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions handling the filtered sources UI.
|
||||
*/
|
||||
function FilteredSourcesView(DebuggerView) {
|
||||
dumpn("FilteredSourcesView was instantiated");
|
||||
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
}
|
||||
|
||||
FilteredSourcesView.prototype = Heritage.extend(ResultsPanelContainer.prototype, {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the FilteredSourcesView");
|
||||
|
||||
this.anchor = document.getElementById("searchbox");
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
this.widget.addEventListener("click", this._onClick, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the FilteredSourcesView");
|
||||
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
this.widget.removeEventListener("click", this._onClick, false);
|
||||
this.anchor = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedules searching for a source.
|
||||
*
|
||||
* @param string aToken
|
||||
* The function to search for.
|
||||
* @param number aWait
|
||||
* The amount of milliseconds to wait until draining.
|
||||
*/
|
||||
scheduleSearch: function(aToken, aWait) {
|
||||
// The amount of time to wait for the requests to settle.
|
||||
let maxDelay = FILE_SEARCH_ACTION_MAX_DELAY;
|
||||
let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
|
||||
|
||||
// Allow requests to settle down first.
|
||||
setNamedTimeout("sources-search", delay, () => this._doSearch(aToken));
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds file matches in all the displayed sources.
|
||||
*
|
||||
* @param string aToken
|
||||
* The string to search for.
|
||||
*/
|
||||
_doSearch: function(aToken, aStore = []) {
|
||||
// Don't continue filtering if the searched token is an empty string.
|
||||
// In contrast with function searching, in this case we don't want to
|
||||
// show a list of all the files when no search token was supplied.
|
||||
if (!aToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let item of this.DebuggerView.Sources.items) {
|
||||
let lowerCaseLabel = item.attachment.label.toLowerCase();
|
||||
let lowerCaseToken = aToken.toLowerCase();
|
||||
if (lowerCaseLabel.match(lowerCaseToken)) {
|
||||
aStore.push(item);
|
||||
}
|
||||
|
||||
// Once the maximum allowed number of results is reached, proceed
|
||||
// with building the UI immediately.
|
||||
if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) {
|
||||
this._syncView(aStore);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't reach the maximum allowed number of results, but that's ok,
|
||||
// continue building the UI.
|
||||
this._syncView(aStore);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the list of sources displayed in this container.
|
||||
*
|
||||
* @param array aSearchResults
|
||||
* The results array, containing search details for each source.
|
||||
*/
|
||||
_syncView: function(aSearchResults) {
|
||||
// If there are no matches found, keep the popup hidden and avoid
|
||||
// creating the view.
|
||||
if (!aSearchResults.length) {
|
||||
window.emit(EVENTS.FILE_SEARCH_MATCH_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let item of aSearchResults) {
|
||||
let url = item.attachment.source.url;
|
||||
|
||||
if (url) {
|
||||
// Create the element node for the location item.
|
||||
let itemView = this._createItemView(
|
||||
SourceUtils.trimUrlLength(item.attachment.label),
|
||||
SourceUtils.trimUrlLength(url, 0, "start")
|
||||
);
|
||||
|
||||
// Append a location item to this container for each match.
|
||||
this.push([itemView], {
|
||||
index: -1, /* specifies on which position should the item be appended */
|
||||
attachment: {
|
||||
url: url
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// There's at least one item displayed in this container. Don't select it
|
||||
// automatically if not forced (by tests) or in tandem with an operator.
|
||||
if (this._autoSelectFirstItem || this.DebuggerView.Filtering.searchOperator) {
|
||||
this.selectedIndex = 0;
|
||||
}
|
||||
this.hidden = false;
|
||||
|
||||
// Signal that file search matches were found and displayed.
|
||||
window.emit(EVENTS.FILE_SEARCH_MATCH_FOUND);
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for this container.
|
||||
*/
|
||||
_onClick: function(e) {
|
||||
let locationItem = this.getItemForElement(e.target);
|
||||
if (locationItem) {
|
||||
this.selectedItem = locationItem;
|
||||
this.DebuggerView.Filtering.clearSearch();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for this container.
|
||||
*
|
||||
* @param object aItem
|
||||
* The item associated with the element to select.
|
||||
*/
|
||||
_onSelect: function({ detail: locationItem }) {
|
||||
if (locationItem) {
|
||||
let actor = this.DebuggerView.Sources.getActorForLocation({ url: locationItem.attachment.url });
|
||||
this.DebuggerView.setEditorLocation(actor, undefined, {
|
||||
noCaret: true,
|
||||
noDebug: true
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Functions handling the function search UI.
|
||||
*/
|
||||
function FilteredFunctionsView(SourceScripts, Parser, DebuggerView) {
|
||||
dumpn("FilteredFunctionsView was instantiated");
|
||||
|
||||
this.SourceScripts = SourceScripts;
|
||||
this.Parser = Parser;
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
}
|
||||
|
||||
FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototype, {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the FilteredFunctionsView");
|
||||
|
||||
this.anchor = document.getElementById("searchbox");
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
this.widget.addEventListener("click", this._onClick, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the FilteredFunctionsView");
|
||||
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
this.widget.removeEventListener("click", this._onClick, false);
|
||||
this.anchor = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedules searching for a function in all of the sources.
|
||||
*
|
||||
* @param string aToken
|
||||
* The function to search for.
|
||||
* @param number aWait
|
||||
* The amount of milliseconds to wait until draining.
|
||||
*/
|
||||
scheduleSearch: function(aToken, aWait) {
|
||||
// The amount of time to wait for the requests to settle.
|
||||
let maxDelay = FUNCTION_SEARCH_ACTION_MAX_DELAY;
|
||||
let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
|
||||
|
||||
// Allow requests to settle down first.
|
||||
setNamedTimeout("function-search", delay, () => {
|
||||
// Start fetching as many sources as possible, then perform the search.
|
||||
let actors = this.DebuggerView.Sources.values;
|
||||
let sourcesFetched = this.SourceScripts.getTextForSources(actors);
|
||||
sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds function matches in all the sources stored in the cache, and groups
|
||||
* them by location and line number.
|
||||
*
|
||||
* @param string aToken
|
||||
* The string to search for.
|
||||
* @param array aSources
|
||||
* An array of [url, text] tuples for each source.
|
||||
*/
|
||||
_doSearch: function(aToken, aSources, aStore = []) {
|
||||
// Continue parsing even if the searched token is an empty string, to
|
||||
// cache the syntax tree nodes generated by the reflection API.
|
||||
|
||||
// Make sure the currently displayed source is parsed first. Once the
|
||||
// maximum allowed number of results are found, parsing will be halted.
|
||||
let currentActor = this.DebuggerView.Sources.selectedValue;
|
||||
let currentSource = aSources.filter(([actor]) => actor == currentActor)[0];
|
||||
aSources.splice(aSources.indexOf(currentSource), 1);
|
||||
aSources.unshift(currentSource);
|
||||
|
||||
// If not searching for a specific function, only parse the displayed source,
|
||||
// which is now the first item in the sources array.
|
||||
if (!aToken) {
|
||||
aSources.splice(1);
|
||||
}
|
||||
|
||||
for (let [actor, contents] of aSources) {
|
||||
let item = this.DebuggerView.Sources.getItemByValue(actor);
|
||||
let url = item.attachment.source.url;
|
||||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parsedSource = this.Parser.get(contents, url);
|
||||
let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken);
|
||||
|
||||
for (let scriptResult of sourceResults) {
|
||||
for (let parseResult of scriptResult) {
|
||||
aStore.push({
|
||||
sourceUrl: scriptResult.sourceUrl,
|
||||
scriptOffset: scriptResult.scriptOffset,
|
||||
functionName: parseResult.functionName,
|
||||
functionLocation: parseResult.functionLocation,
|
||||
inferredName: parseResult.inferredName,
|
||||
inferredChain: parseResult.inferredChain,
|
||||
inferredLocation: parseResult.inferredLocation
|
||||
});
|
||||
|
||||
// Once the maximum allowed number of results is reached, proceed
|
||||
// with building the UI immediately.
|
||||
if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) {
|
||||
this._syncView(aStore);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't reach the maximum allowed number of results, but that's ok,
|
||||
// continue building the UI.
|
||||
this._syncView(aStore);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the list of functions displayed in this container.
|
||||
*
|
||||
* @param array aSearchResults
|
||||
* The results array, containing search details for each source.
|
||||
*/
|
||||
_syncView: function(aSearchResults) {
|
||||
// If there are no matches found, keep the popup hidden and avoid
|
||||
// creating the view.
|
||||
if (!aSearchResults.length) {
|
||||
window.emit(EVENTS.FUNCTION_SEARCH_MATCH_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let item of aSearchResults) {
|
||||
// Some function expressions don't necessarily have a name, but the
|
||||
// parser provides us with an inferred name from an enclosing
|
||||
// VariableDeclarator, AssignmentExpression, ObjectExpression node.
|
||||
if (item.functionName && item.inferredName &&
|
||||
item.functionName != item.inferredName) {
|
||||
let s = " " + L10N.getStr("functionSearchSeparatorLabel") + " ";
|
||||
item.displayedName = item.inferredName + s + item.functionName;
|
||||
}
|
||||
// The function doesn't have an explicit name, but it could be inferred.
|
||||
else if (item.inferredName) {
|
||||
item.displayedName = item.inferredName;
|
||||
}
|
||||
// The function only has an explicit name.
|
||||
else {
|
||||
item.displayedName = item.functionName;
|
||||
}
|
||||
|
||||
// Some function expressions have unexpected bounds, since they may not
|
||||
// necessarily have an associated name defining them.
|
||||
if (item.inferredLocation) {
|
||||
item.actualLocation = item.inferredLocation;
|
||||
} else {
|
||||
item.actualLocation = item.functionLocation;
|
||||
}
|
||||
|
||||
// Create the element node for the function item.
|
||||
let itemView = this._createItemView(
|
||||
SourceUtils.trimUrlLength(item.displayedName + "()"),
|
||||
SourceUtils.trimUrlLength(item.sourceUrl, 0, "start"),
|
||||
(item.inferredChain || []).join(".")
|
||||
);
|
||||
|
||||
// Append a function item to this container for each match.
|
||||
this.push([itemView], {
|
||||
index: -1, /* specifies on which position should the item be appended */
|
||||
attachment: item
|
||||
});
|
||||
}
|
||||
|
||||
// There's at least one item displayed in this container. Don't select it
|
||||
// automatically if not forced (by tests).
|
||||
if (this._autoSelectFirstItem) {
|
||||
this.selectedIndex = 0;
|
||||
}
|
||||
this.hidden = false;
|
||||
|
||||
// Signal that function search matches were found and displayed.
|
||||
window.emit(EVENTS.FUNCTION_SEARCH_MATCH_FOUND);
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for this container.
|
||||
*/
|
||||
_onClick: function(e) {
|
||||
let functionItem = this.getItemForElement(e.target);
|
||||
if (functionItem) {
|
||||
this.selectedItem = functionItem;
|
||||
this.DebuggerView.Filtering.clearSearch();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for this container.
|
||||
*/
|
||||
_onSelect: function({ detail: functionItem }) {
|
||||
if (functionItem) {
|
||||
let sourceUrl = functionItem.attachment.sourceUrl;
|
||||
let actor = this.DebuggerView.Sources.getActorForLocation({ url: sourceUrl });
|
||||
let scriptOffset = functionItem.attachment.scriptOffset;
|
||||
let actualLocation = functionItem.attachment.actualLocation;
|
||||
|
||||
this.DebuggerView.setEditorLocation(actor, actualLocation.start.line, {
|
||||
charOffset: scriptOffset,
|
||||
columnOffset: actualLocation.start.column,
|
||||
align: "center",
|
||||
noDebug: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_searchTimeout: null,
|
||||
_searchFunction: null,
|
||||
_searchedToken: ""
|
||||
});
|
||||
|
||||
DebuggerView.Filtering = new FilterView(DebuggerController, DebuggerView);
|
736
browser/devtools/debugger/views/global-search-view.js
Normal file
736
browser/devtools/debugger/views/global-search-view.js
Normal file
@ -0,0 +1,736 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Functions handling the global search UI.
|
||||
*/
|
||||
function GlobalSearchView(DebuggerController, DebuggerView) {
|
||||
dumpn("GlobalSearchView was instantiated");
|
||||
|
||||
this.SourceScripts = DebuggerController.SourceScripts;
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this._onHeaderClick = this._onHeaderClick.bind(this);
|
||||
this._onLineClick = this._onLineClick.bind(this);
|
||||
this._onMatchClick = this._onMatchClick.bind(this);
|
||||
}
|
||||
|
||||
GlobalSearchView.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the GlobalSearchView");
|
||||
|
||||
this.widget = new SimpleListWidget(document.getElementById("globalsearch"));
|
||||
this._splitter = document.querySelector("#globalsearch + .devtools-horizontal-splitter");
|
||||
|
||||
this.emptyText = L10N.getStr("noMatchingStringsText");
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the GlobalSearchView");
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the results container hidden or visible. It's hidden by default.
|
||||
* @param boolean aFlag
|
||||
*/
|
||||
set hidden(aFlag) {
|
||||
this.widget.setAttribute("hidden", aFlag);
|
||||
this._splitter.setAttribute("hidden", aFlag);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the visibility state of the global search container.
|
||||
* @return boolean
|
||||
*/
|
||||
get hidden()
|
||||
this.widget.getAttribute("hidden") == "true" ||
|
||||
this._splitter.getAttribute("hidden") == "true",
|
||||
|
||||
/**
|
||||
* Hides and removes all items from this search container.
|
||||
*/
|
||||
clearView: function() {
|
||||
this.hidden = true;
|
||||
this.empty();
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects the next found item in this container.
|
||||
* Does not change the currently focused node.
|
||||
*/
|
||||
selectNext: function() {
|
||||
let totalLineResults = LineResults.size();
|
||||
if (!totalLineResults) {
|
||||
return;
|
||||
}
|
||||
if (++this._currentlyFocusedMatch >= totalLineResults) {
|
||||
this._currentlyFocusedMatch = 0;
|
||||
}
|
||||
this._onMatchClick({
|
||||
target: LineResults.getElementAtIndex(this._currentlyFocusedMatch)
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects the previously found item in this container.
|
||||
* Does not change the currently focused node.
|
||||
*/
|
||||
selectPrev: function() {
|
||||
let totalLineResults = LineResults.size();
|
||||
if (!totalLineResults) {
|
||||
return;
|
||||
}
|
||||
if (--this._currentlyFocusedMatch < 0) {
|
||||
this._currentlyFocusedMatch = totalLineResults - 1;
|
||||
}
|
||||
this._onMatchClick({
|
||||
target: LineResults.getElementAtIndex(this._currentlyFocusedMatch)
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedules searching for a string in all of the sources.
|
||||
*
|
||||
* @param string aToken
|
||||
* The string to search for.
|
||||
* @param number aWait
|
||||
* The amount of milliseconds to wait until draining.
|
||||
*/
|
||||
scheduleSearch: function(aToken, aWait) {
|
||||
// The amount of time to wait for the requests to settle.
|
||||
let maxDelay = GLOBAL_SEARCH_ACTION_MAX_DELAY;
|
||||
let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
|
||||
|
||||
// Allow requests to settle down first.
|
||||
setNamedTimeout("global-search", delay, () => {
|
||||
// Start fetching as many sources as possible, then perform the search.
|
||||
let actors = this.DebuggerView.Sources.values;
|
||||
let sourcesFetched = this.SourceScripts.getTextForSources(actors);
|
||||
sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds string matches in all the sources stored in the controller's cache,
|
||||
* and groups them by url and line number.
|
||||
*
|
||||
* @param string aToken
|
||||
* The string to search for.
|
||||
* @param array aSources
|
||||
* An array of [url, text] tuples for each source.
|
||||
*/
|
||||
_doSearch: function(aToken, aSources) {
|
||||
// Don't continue filtering if the searched token is an empty string.
|
||||
if (!aToken) {
|
||||
this.clearView();
|
||||
return;
|
||||
}
|
||||
|
||||
// Search is not case sensitive, prepare the actual searched token.
|
||||
let lowerCaseToken = aToken.toLowerCase();
|
||||
let tokenLength = aToken.length;
|
||||
|
||||
// Create a Map containing search details for each source.
|
||||
let globalResults = new GlobalResults();
|
||||
|
||||
// Search for the specified token in each source's text.
|
||||
for (let [actor, text] of aSources) {
|
||||
let item = this.DebuggerView.Sources.getItemByValue(actor);
|
||||
let url = item.attachment.source.url;
|
||||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify that the search token is found anywhere in the source.
|
||||
if (!text.toLowerCase().contains(lowerCaseToken)) {
|
||||
continue;
|
||||
}
|
||||
// ...and if so, create a Map containing search details for each line.
|
||||
let sourceResults = new SourceResults(actor,
|
||||
globalResults,
|
||||
this.DebuggerView.Sources);
|
||||
|
||||
// Search for the specified token in each line's text.
|
||||
text.split("\n").forEach((aString, aLine) => {
|
||||
// Search is not case sensitive, prepare the actual searched line.
|
||||
let lowerCaseLine = aString.toLowerCase();
|
||||
|
||||
// Verify that the search token is found anywhere in this line.
|
||||
if (!lowerCaseLine.contains(lowerCaseToken)) {
|
||||
return;
|
||||
}
|
||||
// ...and if so, create a Map containing search details for each word.
|
||||
let lineResults = new LineResults(aLine, sourceResults);
|
||||
|
||||
// Search for the specified token this line's text.
|
||||
lowerCaseLine.split(lowerCaseToken).reduce((aPrev, aCurr, aIndex, aArray) => {
|
||||
let prevLength = aPrev.length;
|
||||
let currLength = aCurr.length;
|
||||
|
||||
// Everything before the token is unmatched.
|
||||
let unmatched = aString.substr(prevLength, currLength);
|
||||
lineResults.add(unmatched);
|
||||
|
||||
// The lowered-case line was split by the lowered-case token. So,
|
||||
// get the actual matched text from the original line's text.
|
||||
if (aIndex != aArray.length - 1) {
|
||||
let matched = aString.substr(prevLength + currLength, tokenLength);
|
||||
let range = { start: prevLength + currLength, length: matched.length };
|
||||
lineResults.add(matched, range, true);
|
||||
}
|
||||
|
||||
// Continue with the next sub-region in this line's text.
|
||||
return aPrev + aToken + aCurr;
|
||||
}, "");
|
||||
|
||||
if (lineResults.matchCount) {
|
||||
sourceResults.add(lineResults);
|
||||
}
|
||||
});
|
||||
|
||||
if (sourceResults.matchCount) {
|
||||
globalResults.add(sourceResults);
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the results, then signal if there are any matches.
|
||||
if (globalResults.matchCount) {
|
||||
this.hidden = false;
|
||||
this._currentlyFocusedMatch = -1;
|
||||
this._createGlobalResultsUI(globalResults);
|
||||
window.emit(EVENTS.GLOBAL_SEARCH_MATCH_FOUND);
|
||||
} else {
|
||||
window.emit(EVENTS.GLOBAL_SEARCH_MATCH_NOT_FOUND);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates global search results entries and adds them to this container.
|
||||
*
|
||||
* @param GlobalResults aGlobalResults
|
||||
* An object containing all source results, grouped by source location.
|
||||
*/
|
||||
_createGlobalResultsUI: function(aGlobalResults) {
|
||||
let i = 0;
|
||||
|
||||
for (let sourceResults of aGlobalResults) {
|
||||
if (i++ == 0) {
|
||||
this._createSourceResultsUI(sourceResults);
|
||||
} else {
|
||||
// Dispatch subsequent document manipulation operations, to avoid
|
||||
// blocking the main thread when a large number of search results
|
||||
// is found, thus giving the impression of faster searching.
|
||||
Services.tm.currentThread.dispatch({ run:
|
||||
this._createSourceResultsUI.bind(this, sourceResults)
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates source search results entries and adds them to this container.
|
||||
*
|
||||
* @param SourceResults aSourceResults
|
||||
* An object containing all the matched lines for a specific source.
|
||||
*/
|
||||
_createSourceResultsUI: function(aSourceResults) {
|
||||
// Create the element node for the source results item.
|
||||
let container = document.createElement("hbox");
|
||||
aSourceResults.createView(container, {
|
||||
onHeaderClick: this._onHeaderClick,
|
||||
onLineClick: this._onLineClick,
|
||||
onMatchClick: this._onMatchClick
|
||||
});
|
||||
|
||||
// Append a source results item to this container.
|
||||
let item = this.push([container], {
|
||||
index: -1, /* specifies on which position should the item be appended */
|
||||
attachment: {
|
||||
sourceResults: aSourceResults
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for a results header.
|
||||
*/
|
||||
_onHeaderClick: function(e) {
|
||||
let sourceResultsItem = SourceResults.getItemForElement(e.target);
|
||||
sourceResultsItem.instance.toggle(e);
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for a results line.
|
||||
*/
|
||||
_onLineClick: function(e) {
|
||||
let lineResultsItem = LineResults.getItemForElement(e.target);
|
||||
this._onMatchClick({ target: lineResultsItem.firstMatch });
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for a result match.
|
||||
*/
|
||||
_onMatchClick: function(e) {
|
||||
if (e instanceof Event) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
let target = e.target;
|
||||
let sourceResultsItem = SourceResults.getItemForElement(target);
|
||||
let lineResultsItem = LineResults.getItemForElement(target);
|
||||
|
||||
sourceResultsItem.instance.expand();
|
||||
this._currentlyFocusedMatch = LineResults.indexOfElement(target);
|
||||
this._scrollMatchIntoViewIfNeeded(target);
|
||||
this._bounceMatch(target);
|
||||
|
||||
let actor = sourceResultsItem.instance.actor;
|
||||
let line = lineResultsItem.instance.line;
|
||||
|
||||
this.DebuggerView.setEditorLocation(actor, line + 1, { noDebug: true });
|
||||
|
||||
let range = lineResultsItem.lineData.range;
|
||||
let cursor = this.DebuggerView.editor.getOffset({ line: line, ch: 0 });
|
||||
let [ anchor, head ] = this.DebuggerView.editor.getPosition(
|
||||
cursor + range.start,
|
||||
cursor + range.start + range.length
|
||||
);
|
||||
|
||||
this.DebuggerView.editor.setSelection(anchor, head);
|
||||
},
|
||||
|
||||
/**
|
||||
* Scrolls a match into view if not already visible.
|
||||
*
|
||||
* @param nsIDOMNode aMatch
|
||||
* The match to scroll into view.
|
||||
*/
|
||||
_scrollMatchIntoViewIfNeeded: function(aMatch) {
|
||||
this.widget.ensureElementIsVisible(aMatch);
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a bounce animation for a match.
|
||||
*
|
||||
* @param nsIDOMNode aMatch
|
||||
* The match to start a bounce animation for.
|
||||
*/
|
||||
_bounceMatch: function(aMatch) {
|
||||
Services.tm.currentThread.dispatch({ run: () => {
|
||||
aMatch.addEventListener("transitionend", function onEvent() {
|
||||
aMatch.removeEventListener("transitionend", onEvent);
|
||||
aMatch.removeAttribute("focused");
|
||||
});
|
||||
aMatch.setAttribute("focused", "");
|
||||
}}, 0);
|
||||
aMatch.setAttribute("focusing", "");
|
||||
},
|
||||
|
||||
_splitter: null,
|
||||
_currentlyFocusedMatch: -1,
|
||||
_forceExpandResults: false
|
||||
});
|
||||
|
||||
DebuggerView.GlobalSearch = new GlobalSearchView(DebuggerController, DebuggerView);
|
||||
|
||||
/**
|
||||
* An object containing all source results, grouped by source location.
|
||||
* Iterable via "for (let [location, sourceResults] of globalResults) { }".
|
||||
*/
|
||||
function GlobalResults() {
|
||||
this._store = [];
|
||||
SourceResults._itemsByElement = new Map();
|
||||
LineResults._itemsByElement = new Map();
|
||||
}
|
||||
|
||||
GlobalResults.prototype = {
|
||||
/**
|
||||
* Adds source results to this store.
|
||||
*
|
||||
* @param SourceResults aSourceResults
|
||||
* An object containing search results for a specific source.
|
||||
*/
|
||||
add: function(aSourceResults) {
|
||||
this._store.push(aSourceResults);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the number of source results in this store.
|
||||
*/
|
||||
get matchCount() this._store.length
|
||||
};
|
||||
|
||||
/**
|
||||
* An object containing all the matched lines for a specific source.
|
||||
* Iterable via "for (let [lineNumber, lineResults] of sourceResults) { }".
|
||||
*
|
||||
* @param string aActor
|
||||
* The target source actor id.
|
||||
* @param GlobalResults aGlobalResults
|
||||
* An object containing all source results, grouped by source location.
|
||||
*/
|
||||
function SourceResults(aActor, aGlobalResults, sourcesView) {
|
||||
let item = sourcesView.getItemByValue(aActor);
|
||||
this.actor = aActor;
|
||||
this.label = item.attachment.source.url;
|
||||
this._globalResults = aGlobalResults;
|
||||
this._store = [];
|
||||
}
|
||||
|
||||
SourceResults.prototype = {
|
||||
/**
|
||||
* Adds line results to this store.
|
||||
*
|
||||
* @param LineResults aLineResults
|
||||
* An object containing search results for a specific line.
|
||||
*/
|
||||
add: function(aLineResults) {
|
||||
this._store.push(aLineResults);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the number of line results in this store.
|
||||
*/
|
||||
get matchCount() this._store.length,
|
||||
|
||||
/**
|
||||
* Expands the element, showing all the added details.
|
||||
*/
|
||||
expand: function() {
|
||||
this._resultsContainer.removeAttribute("hidden");
|
||||
this._arrow.setAttribute("open", "");
|
||||
},
|
||||
|
||||
/**
|
||||
* Collapses the element, hiding all the added details.
|
||||
*/
|
||||
collapse: function() {
|
||||
this._resultsContainer.setAttribute("hidden", "true");
|
||||
this._arrow.removeAttribute("open");
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles between the element collapse/expand state.
|
||||
*/
|
||||
toggle: function(e) {
|
||||
this.expanded ^= 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets this element's expanded state.
|
||||
* @return boolean
|
||||
*/
|
||||
get expanded()
|
||||
this._resultsContainer.getAttribute("hidden") != "true" &&
|
||||
this._arrow.hasAttribute("open"),
|
||||
|
||||
/**
|
||||
* Sets this element's expanded state.
|
||||
* @param boolean aFlag
|
||||
*/
|
||||
set expanded(aFlag) this[aFlag ? "expand" : "collapse"](),
|
||||
|
||||
/**
|
||||
* Gets the element associated with this item.
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
get target() this._target,
|
||||
|
||||
/**
|
||||
* Customization function for creating this item's UI.
|
||||
*
|
||||
* @param nsIDOMNode aElementNode
|
||||
* The element associated with the displayed item.
|
||||
* @param object aCallbacks
|
||||
* An object containing all the necessary callback functions:
|
||||
* - onHeaderClick
|
||||
* - onMatchClick
|
||||
*/
|
||||
createView: function(aElementNode, aCallbacks) {
|
||||
this._target = aElementNode;
|
||||
|
||||
let arrow = this._arrow = document.createElement("box");
|
||||
arrow.className = "arrow";
|
||||
|
||||
let locationNode = document.createElement("label");
|
||||
locationNode.className = "plain dbg-results-header-location";
|
||||
locationNode.setAttribute("value", this.label);
|
||||
|
||||
let matchCountNode = document.createElement("label");
|
||||
matchCountNode.className = "plain dbg-results-header-match-count";
|
||||
matchCountNode.setAttribute("value", "(" + this.matchCount + ")");
|
||||
|
||||
let resultsHeader = this._resultsHeader = document.createElement("hbox");
|
||||
resultsHeader.className = "dbg-results-header";
|
||||
resultsHeader.setAttribute("align", "center")
|
||||
resultsHeader.appendChild(arrow);
|
||||
resultsHeader.appendChild(locationNode);
|
||||
resultsHeader.appendChild(matchCountNode);
|
||||
resultsHeader.addEventListener("click", aCallbacks.onHeaderClick, false);
|
||||
|
||||
let resultsContainer = this._resultsContainer = document.createElement("vbox");
|
||||
resultsContainer.className = "dbg-results-container";
|
||||
resultsContainer.setAttribute("hidden", "true");
|
||||
|
||||
// Create lines search results entries and add them to this container.
|
||||
// Afterwards, if the number of matches is reasonable, expand this
|
||||
// container automatically.
|
||||
for (let lineResults of this._store) {
|
||||
lineResults.createView(resultsContainer, aCallbacks);
|
||||
}
|
||||
if (this.matchCount < GLOBAL_SEARCH_EXPAND_MAX_RESULTS) {
|
||||
this.expand();
|
||||
}
|
||||
|
||||
let resultsBox = document.createElement("vbox");
|
||||
resultsBox.setAttribute("flex", "1");
|
||||
resultsBox.appendChild(resultsHeader);
|
||||
resultsBox.appendChild(resultsContainer);
|
||||
|
||||
aElementNode.id = "source-results-" + this.actor;
|
||||
aElementNode.className = "dbg-source-results";
|
||||
aElementNode.appendChild(resultsBox);
|
||||
|
||||
SourceResults._itemsByElement.set(aElementNode, { instance: this });
|
||||
},
|
||||
|
||||
actor: "",
|
||||
_globalResults: null,
|
||||
_store: null,
|
||||
_target: null,
|
||||
_arrow: null,
|
||||
_resultsHeader: null,
|
||||
_resultsContainer: null
|
||||
};
|
||||
|
||||
/**
|
||||
* An object containing all the matches for a specific line.
|
||||
* Iterable via "for (let chunk of lineResults) { }".
|
||||
*
|
||||
* @param number aLine
|
||||
* The target line in the source.
|
||||
* @param SourceResults aSourceResults
|
||||
* An object containing all the matched lines for a specific source.
|
||||
*/
|
||||
function LineResults(aLine, aSourceResults) {
|
||||
this.line = aLine;
|
||||
this._sourceResults = aSourceResults;
|
||||
this._store = [];
|
||||
this._matchCount = 0;
|
||||
}
|
||||
|
||||
LineResults.prototype = {
|
||||
/**
|
||||
* Adds string details to this store.
|
||||
*
|
||||
* @param string aString
|
||||
* The text contents chunk in the line.
|
||||
* @param object aRange
|
||||
* An object containing the { start, length } of the chunk.
|
||||
* @param boolean aMatchFlag
|
||||
* True if the chunk is a matched string, false if just text content.
|
||||
*/
|
||||
add: function(aString, aRange, aMatchFlag) {
|
||||
this._store.push({ string: aString, range: aRange, match: !!aMatchFlag });
|
||||
this._matchCount += aMatchFlag ? 1 : 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the number of word results in this store.
|
||||
*/
|
||||
get matchCount() this._matchCount,
|
||||
|
||||
/**
|
||||
* Gets the element associated with this item.
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
get target() this._target,
|
||||
|
||||
/**
|
||||
* Customization function for creating this item's UI.
|
||||
*
|
||||
* @param nsIDOMNode aElementNode
|
||||
* The element associated with the displayed item.
|
||||
* @param object aCallbacks
|
||||
* An object containing all the necessary callback functions:
|
||||
* - onMatchClick
|
||||
* - onLineClick
|
||||
*/
|
||||
createView: function(aElementNode, aCallbacks) {
|
||||
this._target = aElementNode;
|
||||
|
||||
let lineNumberNode = document.createElement("label");
|
||||
lineNumberNode.className = "plain dbg-results-line-number";
|
||||
lineNumberNode.classList.add("devtools-monospace");
|
||||
lineNumberNode.setAttribute("value", this.line + 1);
|
||||
|
||||
let lineContentsNode = document.createElement("hbox");
|
||||
lineContentsNode.className = "dbg-results-line-contents";
|
||||
lineContentsNode.classList.add("devtools-monospace");
|
||||
lineContentsNode.setAttribute("flex", "1");
|
||||
|
||||
let lineString = "";
|
||||
let lineLength = 0;
|
||||
let firstMatch = null;
|
||||
|
||||
for (let lineChunk of this._store) {
|
||||
let { string, range, match } = lineChunk;
|
||||
lineString = string.substr(0, GLOBAL_SEARCH_LINE_MAX_LENGTH - lineLength);
|
||||
lineLength += string.length;
|
||||
|
||||
let lineChunkNode = document.createElement("label");
|
||||
lineChunkNode.className = "plain dbg-results-line-contents-string";
|
||||
lineChunkNode.setAttribute("value", lineString);
|
||||
lineChunkNode.setAttribute("match", match);
|
||||
lineContentsNode.appendChild(lineChunkNode);
|
||||
|
||||
if (match) {
|
||||
this._entangleMatch(lineChunkNode, lineChunk);
|
||||
lineChunkNode.addEventListener("click", aCallbacks.onMatchClick, false);
|
||||
firstMatch = firstMatch || lineChunkNode;
|
||||
}
|
||||
if (lineLength >= GLOBAL_SEARCH_LINE_MAX_LENGTH) {
|
||||
lineContentsNode.appendChild(this._ellipsis.cloneNode(true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._entangleLine(lineContentsNode, firstMatch);
|
||||
lineContentsNode.addEventListener("click", aCallbacks.onLineClick, false);
|
||||
|
||||
let searchResult = document.createElement("hbox");
|
||||
searchResult.className = "dbg-search-result";
|
||||
searchResult.appendChild(lineNumberNode);
|
||||
searchResult.appendChild(lineContentsNode);
|
||||
|
||||
aElementNode.appendChild(searchResult);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles a match while creating the view.
|
||||
* @param nsIDOMNode aNode
|
||||
* @param object aMatchChunk
|
||||
*/
|
||||
_entangleMatch: function(aNode, aMatchChunk) {
|
||||
LineResults._itemsByElement.set(aNode, {
|
||||
instance: this,
|
||||
lineData: aMatchChunk
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles a line while creating the view.
|
||||
* @param nsIDOMNode aNode
|
||||
* @param nsIDOMNode aFirstMatch
|
||||
*/
|
||||
_entangleLine: function(aNode, aFirstMatch) {
|
||||
LineResults._itemsByElement.set(aNode, {
|
||||
instance: this,
|
||||
firstMatch: aFirstMatch,
|
||||
ignored: true
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* An nsIDOMNode label with an ellipsis value.
|
||||
*/
|
||||
_ellipsis: (function() {
|
||||
let label = document.createElement("label");
|
||||
label.className = "plain dbg-results-line-contents-string";
|
||||
label.setAttribute("value", L10N.ellipsis);
|
||||
return label;
|
||||
})(),
|
||||
|
||||
line: 0,
|
||||
_sourceResults: null,
|
||||
_store: null,
|
||||
_target: null
|
||||
};
|
||||
|
||||
/**
|
||||
* A generator-iterator over the global, source or line results.
|
||||
*/
|
||||
GlobalResults.prototype[Symbol.iterator] =
|
||||
SourceResults.prototype[Symbol.iterator] =
|
||||
LineResults.prototype[Symbol.iterator] = function*() {
|
||||
yield* this._store;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the item associated with the specified element.
|
||||
*
|
||||
* @param nsIDOMNode aElement
|
||||
* The element used to identify the item.
|
||||
* @return object
|
||||
* The matched item, or null if nothing is found.
|
||||
*/
|
||||
SourceResults.getItemForElement =
|
||||
LineResults.getItemForElement = function(aElement) {
|
||||
return WidgetMethods.getItemForElement.call(this, aElement, { noSiblings: true });
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the element associated with a particular item at a specified index.
|
||||
*
|
||||
* @param number aIndex
|
||||
* The index used to identify the item.
|
||||
* @return nsIDOMNode
|
||||
* The matched element, or null if nothing is found.
|
||||
*/
|
||||
SourceResults.getElementAtIndex =
|
||||
LineResults.getElementAtIndex = function(aIndex) {
|
||||
for (let [element, item] of this._itemsByElement) {
|
||||
if (!item.ignored && !aIndex--) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the index of an item associated with the specified element.
|
||||
*
|
||||
* @param nsIDOMNode aElement
|
||||
* The element to get the index for.
|
||||
* @return number
|
||||
* The index of the matched element, or -1 if nothing is found.
|
||||
*/
|
||||
SourceResults.indexOfElement =
|
||||
LineResults.indexOfElement = function(aElement) {
|
||||
let count = 0;
|
||||
for (let [element, item] of this._itemsByElement) {
|
||||
if (element == aElement) {
|
||||
return count;
|
||||
}
|
||||
if (!item.ignored) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the number of cached items associated with a specified element.
|
||||
*
|
||||
* @return number
|
||||
* The number of key/value pairs in the corresponding map.
|
||||
*/
|
||||
SourceResults.size =
|
||||
LineResults.size = function() {
|
||||
let count = 0;
|
||||
for (let [, item] of this._itemsByElement) {
|
||||
if (!item.ignored) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
209
browser/devtools/debugger/views/options-view.js
Normal file
209
browser/devtools/debugger/views/options-view.js
Normal file
@ -0,0 +1,209 @@
|
||||
/* 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";
|
||||
|
||||
// A time interval sufficient for the options popup panel to finish hiding
|
||||
// itself.
|
||||
const POPUP_HIDDEN_DELAY = 100; // ms
|
||||
|
||||
/**
|
||||
* Functions handling the options UI.
|
||||
*/
|
||||
function OptionsView(DebuggerController, DebuggerView) {
|
||||
dumpn("OptionsView was instantiated");
|
||||
|
||||
this.DebuggerController = DebuggerController;
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this._toggleAutoPrettyPrint = this._toggleAutoPrettyPrint.bind(this);
|
||||
this._togglePauseOnExceptions = this._togglePauseOnExceptions.bind(this);
|
||||
this._toggleIgnoreCaughtExceptions = this._toggleIgnoreCaughtExceptions.bind(this);
|
||||
this._toggleShowPanesOnStartup = this._toggleShowPanesOnStartup.bind(this);
|
||||
this._toggleShowVariablesOnlyEnum = this._toggleShowVariablesOnlyEnum.bind(this);
|
||||
this._toggleShowVariablesFilterBox = this._toggleShowVariablesFilterBox.bind(this);
|
||||
this._toggleShowOriginalSource = this._toggleShowOriginalSource.bind(this);
|
||||
this._toggleAutoBlackBox = this._toggleAutoBlackBox.bind(this);
|
||||
}
|
||||
|
||||
OptionsView.prototype = {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the OptionsView");
|
||||
|
||||
this._button = document.getElementById("debugger-options");
|
||||
this._autoPrettyPrint = document.getElementById("auto-pretty-print");
|
||||
this._pauseOnExceptionsItem = document.getElementById("pause-on-exceptions");
|
||||
this._ignoreCaughtExceptionsItem = document.getElementById("ignore-caught-exceptions");
|
||||
this._showPanesOnStartupItem = document.getElementById("show-panes-on-startup");
|
||||
this._showVariablesOnlyEnumItem = document.getElementById("show-vars-only-enum");
|
||||
this._showVariablesFilterBoxItem = document.getElementById("show-vars-filter-box");
|
||||
this._showOriginalSourceItem = document.getElementById("show-original-source");
|
||||
this._autoBlackBoxItem = document.getElementById("auto-black-box");
|
||||
|
||||
this._autoPrettyPrint.setAttribute("checked", Prefs.autoPrettyPrint);
|
||||
this._pauseOnExceptionsItem.setAttribute("checked", Prefs.pauseOnExceptions);
|
||||
this._ignoreCaughtExceptionsItem.setAttribute("checked", Prefs.ignoreCaughtExceptions);
|
||||
this._showPanesOnStartupItem.setAttribute("checked", Prefs.panesVisibleOnStartup);
|
||||
this._showVariablesOnlyEnumItem.setAttribute("checked", Prefs.variablesOnlyEnumVisible);
|
||||
this._showVariablesFilterBoxItem.setAttribute("checked", Prefs.variablesSearchboxVisible);
|
||||
this._showOriginalSourceItem.setAttribute("checked", Prefs.sourceMapsEnabled);
|
||||
this._autoBlackBoxItem.setAttribute("checked", Prefs.autoBlackBox);
|
||||
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the OptionsView");
|
||||
// Nothing to do here yet.
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
XULUtils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
toggleAutoPrettyPrint: () => this._toggleAutoPrettyPrint(),
|
||||
togglePauseOnExceptions: () => this._togglePauseOnExceptions(),
|
||||
toggleIgnoreCaughtExceptions: () => this._toggleIgnoreCaughtExceptions(),
|
||||
toggleShowPanesOnStartup: () => this._toggleShowPanesOnStartup(),
|
||||
toggleShowOnlyEnum: () => this._toggleShowVariablesOnlyEnum(),
|
||||
toggleShowVariablesFilterBox: () => this._toggleShowVariablesFilterBox(),
|
||||
toggleShowOriginalSource: () => this._toggleShowOriginalSource(),
|
||||
toggleAutoBlackBox: () => this._toggleAutoBlackBox()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'gear menu' popup showing event.
|
||||
*/
|
||||
_onPopupShowing: function() {
|
||||
this._button.setAttribute("open", "true");
|
||||
window.emit(EVENTS.OPTIONS_POPUP_SHOWING);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'gear menu' popup hiding event.
|
||||
*/
|
||||
_onPopupHiding: function() {
|
||||
this._button.removeAttribute("open");
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'gear menu' popup hidden event.
|
||||
*/
|
||||
_onPopupHidden: function() {
|
||||
window.emit(EVENTS.OPTIONS_POPUP_HIDDEN);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'auto pretty print' menuitem command.
|
||||
*/
|
||||
_toggleAutoPrettyPrint: function(){
|
||||
Prefs.autoPrettyPrint =
|
||||
this._autoPrettyPrint.getAttribute("checked") == "true";
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'pause on exceptions' menuitem command.
|
||||
*/
|
||||
_togglePauseOnExceptions: function() {
|
||||
Prefs.pauseOnExceptions =
|
||||
this._pauseOnExceptionsItem.getAttribute("checked") == "true";
|
||||
|
||||
this.DebuggerController.activeThread.pauseOnExceptions(
|
||||
Prefs.pauseOnExceptions,
|
||||
Prefs.ignoreCaughtExceptions);
|
||||
},
|
||||
|
||||
_toggleIgnoreCaughtExceptions: function() {
|
||||
Prefs.ignoreCaughtExceptions =
|
||||
this._ignoreCaughtExceptionsItem.getAttribute("checked") == "true";
|
||||
|
||||
this.DebuggerController.activeThread.pauseOnExceptions(
|
||||
Prefs.pauseOnExceptions,
|
||||
Prefs.ignoreCaughtExceptions);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'show panes on startup' menuitem command.
|
||||
*/
|
||||
_toggleShowPanesOnStartup: function() {
|
||||
Prefs.panesVisibleOnStartup =
|
||||
this._showPanesOnStartupItem.getAttribute("checked") == "true";
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'show non-enumerables' menuitem command.
|
||||
*/
|
||||
_toggleShowVariablesOnlyEnum: function() {
|
||||
let pref = Prefs.variablesOnlyEnumVisible =
|
||||
this._showVariablesOnlyEnumItem.getAttribute("checked") == "true";
|
||||
|
||||
this.DebuggerView.Variables.onlyEnumVisible = pref;
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'show variables searchbox' menuitem command.
|
||||
*/
|
||||
_toggleShowVariablesFilterBox: function() {
|
||||
let pref = Prefs.variablesSearchboxVisible =
|
||||
this._showVariablesFilterBoxItem.getAttribute("checked") == "true";
|
||||
|
||||
this.DebuggerView.Variables.searchEnabled = pref;
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'show original source' menuitem command.
|
||||
*/
|
||||
_toggleShowOriginalSource: function() {
|
||||
let pref = Prefs.sourceMapsEnabled =
|
||||
this._showOriginalSourceItem.getAttribute("checked") == "true";
|
||||
|
||||
// Don't block the UI while reconfiguring the server.
|
||||
window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => {
|
||||
// The popup panel needs more time to hide after triggering onpopuphidden.
|
||||
window.setTimeout(() => {
|
||||
this.DebuggerController.reconfigureThread({
|
||||
useSourceMaps: pref,
|
||||
autoBlackBox: Prefs.autoBlackBox
|
||||
});
|
||||
}, POPUP_HIDDEN_DELAY);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'automatically black box minified sources' menuitem
|
||||
* command.
|
||||
*/
|
||||
_toggleAutoBlackBox: function() {
|
||||
let pref = Prefs.autoBlackBox =
|
||||
this._autoBlackBoxItem.getAttribute("checked") == "true";
|
||||
|
||||
// Don't block the UI while reconfiguring the server.
|
||||
window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => {
|
||||
// The popup panel needs more time to hide after triggering onpopuphidden.
|
||||
window.setTimeout(() => {
|
||||
this.DebuggerController.reconfigureThread({
|
||||
useSourceMaps: Prefs.sourceMapsEnabled,
|
||||
autoBlackBox: pref
|
||||
});
|
||||
}, POPUP_HIDDEN_DELAY);
|
||||
});
|
||||
},
|
||||
|
||||
_button: null,
|
||||
_pauseOnExceptionsItem: null,
|
||||
_showPanesOnStartupItem: null,
|
||||
_showVariablesOnlyEnumItem: null,
|
||||
_showVariablesFilterBoxItem: null,
|
||||
_showOriginalSourceItem: null,
|
||||
_autoBlackBoxItem: null
|
||||
};
|
||||
|
||||
DebuggerView.Options = new OptionsView(DebuggerController, DebuggerView);
|
1256
browser/devtools/debugger/views/sources-view.js
Normal file
1256
browser/devtools/debugger/views/sources-view.js
Normal file
File diff suppressed because it is too large
Load Diff
135
browser/devtools/debugger/views/stack-frames-classic-view.js
Normal file
135
browser/devtools/debugger/views/stack-frames-classic-view.js
Normal file
@ -0,0 +1,135 @@
|
||||
/* 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";
|
||||
|
||||
/*
|
||||
* Functions handling the stackframes classic list UI.
|
||||
* Controlled by the DebuggerView.StackFrames isntance.
|
||||
*/
|
||||
function StackFramesClassicListView(DebuggerController, DebuggerView) {
|
||||
dumpn("StackFramesClassicListView was instantiated");
|
||||
|
||||
this.DebuggerView = DebuggerView;
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
}
|
||||
|
||||
StackFramesClassicListView.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the StackFramesClassicListView");
|
||||
|
||||
this.widget = new SideMenuWidget(document.getElementById("callstack-list"));
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
|
||||
this.emptyText = L10N.getStr("noStackFramesText");
|
||||
this.autoFocusOnFirstItem = false;
|
||||
this.autoFocusOnSelection = false;
|
||||
|
||||
// This view's contents are also mirrored in a different container.
|
||||
this._mirror = this.DebuggerView.StackFrames;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the StackFramesClassicListView");
|
||||
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a frame in this stackframes container.
|
||||
*
|
||||
* @param string aTitle
|
||||
* The frame title (function name).
|
||||
* @param string aUrl
|
||||
* The frame source url.
|
||||
* @param string aLine
|
||||
* The frame line number.
|
||||
* @param number aDepth
|
||||
* The frame depth in the stack.
|
||||
*/
|
||||
addFrame: function(aTitle, aUrl, aLine, aDepth) {
|
||||
// Create the element node for the stack frame item.
|
||||
let frameView = this._createFrameView.apply(this, arguments);
|
||||
|
||||
// Append a stack frame item to this container.
|
||||
this.push([frameView], {
|
||||
attachment: {
|
||||
depth: aDepth
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Customization function for creating an item's UI.
|
||||
*
|
||||
* @param string aTitle
|
||||
* The frame title to be displayed in the list.
|
||||
* @param string aUrl
|
||||
* The frame source url.
|
||||
* @param string aLine
|
||||
* The frame line number.
|
||||
* @param number aDepth
|
||||
* The frame depth in the stack.
|
||||
* @return nsIDOMNode
|
||||
* The stack frame view.
|
||||
*/
|
||||
_createFrameView: function(aTitle, aUrl, aLine, aDepth) {
|
||||
let container = document.createElement("hbox");
|
||||
container.id = "classic-stackframe-" + aDepth;
|
||||
container.className = "dbg-classic-stackframe";
|
||||
container.setAttribute("flex", "1");
|
||||
|
||||
let frameTitleNode = document.createElement("label");
|
||||
frameTitleNode.className = "plain dbg-classic-stackframe-title";
|
||||
frameTitleNode.setAttribute("value", aTitle);
|
||||
frameTitleNode.setAttribute("crop", "center");
|
||||
|
||||
let frameDetailsNode = document.createElement("hbox");
|
||||
frameDetailsNode.className = "plain dbg-classic-stackframe-details";
|
||||
|
||||
let frameUrlNode = document.createElement("label");
|
||||
frameUrlNode.className = "plain dbg-classic-stackframe-details-url";
|
||||
frameUrlNode.setAttribute("value", SourceUtils.getSourceLabel(aUrl));
|
||||
frameUrlNode.setAttribute("crop", "center");
|
||||
frameDetailsNode.appendChild(frameUrlNode);
|
||||
|
||||
let frameDetailsSeparator = document.createElement("label");
|
||||
frameDetailsSeparator.className = "plain dbg-classic-stackframe-details-sep";
|
||||
frameDetailsSeparator.setAttribute("value", SEARCH_LINE_FLAG);
|
||||
frameDetailsNode.appendChild(frameDetailsSeparator);
|
||||
|
||||
let frameLineNode = document.createElement("label");
|
||||
frameLineNode.className = "plain dbg-classic-stackframe-details-line";
|
||||
frameLineNode.setAttribute("value", aLine);
|
||||
frameDetailsNode.appendChild(frameLineNode);
|
||||
|
||||
container.appendChild(frameTitleNode);
|
||||
container.appendChild(frameDetailsNode);
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for the stackframes container.
|
||||
*/
|
||||
_onSelect: function(e) {
|
||||
let stackframeItem = this.selectedItem;
|
||||
if (stackframeItem) {
|
||||
// The container is not empty and an actual item was selected.
|
||||
// Mirror the selected item in the breadcrumbs list.
|
||||
let depth = stackframeItem.attachment.depth;
|
||||
this._mirror.selectedItem = e => e.attachment.depth == depth;
|
||||
}
|
||||
},
|
||||
|
||||
_mirror: null
|
||||
});
|
||||
|
||||
DebuggerView.StackFramesClassicList = new StackFramesClassicListView(DebuggerController,
|
||||
DebuggerView);
|
236
browser/devtools/debugger/views/stack-frames-view.js
Normal file
236
browser/devtools/debugger/views/stack-frames-view.js
Normal file
@ -0,0 +1,236 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Functions handling the stackframes UI.
|
||||
*/
|
||||
function StackFramesView(DebuggerController, DebuggerView) {
|
||||
dumpn("StackFramesView was instantiated");
|
||||
|
||||
this.StackFrames = DebuggerController.StackFrames;
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this._onStackframeRemoved = this._onStackframeRemoved.bind(this);
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._onScroll = this._onScroll.bind(this);
|
||||
this._afterScroll = this._afterScroll.bind(this);
|
||||
}
|
||||
|
||||
StackFramesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the StackFramesView");
|
||||
|
||||
this.widget = new BreadcrumbsWidget(document.getElementById("stackframes"));
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
this.widget.addEventListener("scroll", this._onScroll, true);
|
||||
window.addEventListener("resize", this._onScroll, true);
|
||||
|
||||
this.autoFocusOnFirstItem = false;
|
||||
this.autoFocusOnSelection = false;
|
||||
|
||||
// This view's contents are also mirrored in a different container.
|
||||
this._mirror = this.DebuggerView.StackFramesClassicList;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the StackFramesView");
|
||||
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
this.widget.removeEventListener("scroll", this._onScroll, true);
|
||||
window.removeEventListener("resize", this._onScroll, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a frame in this stackframes container.
|
||||
*
|
||||
* @param string aTitle
|
||||
* The frame title (function name).
|
||||
* @param string aUrl
|
||||
* The frame source url.
|
||||
* @param string aLine
|
||||
* The frame line number.
|
||||
* @param number aDepth
|
||||
* The frame depth in the stack.
|
||||
* @param boolean aIsBlackBoxed
|
||||
* Whether or not the frame is black boxed.
|
||||
*/
|
||||
addFrame: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
|
||||
// Blackboxed stack frames are collapsed into a single entry in
|
||||
// the view. By convention, only the first frame is displayed.
|
||||
if (aIsBlackBoxed) {
|
||||
if (this._prevBlackBoxedUrl == aUrl) {
|
||||
return;
|
||||
}
|
||||
this._prevBlackBoxedUrl = aUrl;
|
||||
} else {
|
||||
this._prevBlackBoxedUrl = null;
|
||||
}
|
||||
|
||||
// Create the element node for the stack frame item.
|
||||
let frameView = this._createFrameView.apply(this, arguments);
|
||||
|
||||
// Append a stack frame item to this container.
|
||||
this.push([frameView], {
|
||||
index: 0, /* specifies on which position should the item be appended */
|
||||
attachment: {
|
||||
title: aTitle,
|
||||
url: aUrl,
|
||||
line: aLine,
|
||||
depth: aDepth
|
||||
},
|
||||
// Make sure that when the stack frame item is removed, the corresponding
|
||||
// mirrored item in the classic list is also removed.
|
||||
finalize: this._onStackframeRemoved
|
||||
});
|
||||
|
||||
// Mirror this newly inserted item inside the "Call Stack" tab.
|
||||
this._mirror.addFrame(aTitle, aUrl, aLine, aDepth);
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects the frame at the specified depth in this container.
|
||||
* @param number aDepth
|
||||
*/
|
||||
set selectedDepth(aDepth) {
|
||||
this.selectedItem = aItem => aItem.attachment.depth == aDepth;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the currently selected stack frame's depth in this container.
|
||||
* This will essentially be the opposite of |selectedIndex|, which deals
|
||||
* with the position in the view, where the last item added is actually
|
||||
* the bottommost, not topmost.
|
||||
* @return number
|
||||
*/
|
||||
get selectedDepth() {
|
||||
return this.selectedItem.attachment.depth;
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies if the active thread has more frames that need to be loaded.
|
||||
*/
|
||||
dirty: false,
|
||||
|
||||
/**
|
||||
* Customization function for creating an item's UI.
|
||||
*
|
||||
* @param string aTitle
|
||||
* The frame title to be displayed in the list.
|
||||
* @param string aUrl
|
||||
* The frame source url.
|
||||
* @param string aLine
|
||||
* The frame line number.
|
||||
* @param number aDepth
|
||||
* The frame depth in the stack.
|
||||
* @param boolean aIsBlackBoxed
|
||||
* Whether or not the frame is black boxed.
|
||||
* @return nsIDOMNode
|
||||
* The stack frame view.
|
||||
*/
|
||||
_createFrameView: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
|
||||
let container = document.createElement("hbox");
|
||||
container.id = "stackframe-" + aDepth;
|
||||
container.className = "dbg-stackframe";
|
||||
|
||||
let frameDetails = SourceUtils.trimUrlLength(
|
||||
SourceUtils.getSourceLabel(aUrl),
|
||||
STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
|
||||
STACK_FRAMES_SOURCE_URL_TRIM_SECTION);
|
||||
|
||||
if (aIsBlackBoxed) {
|
||||
container.classList.add("dbg-stackframe-black-boxed");
|
||||
} else {
|
||||
let frameTitleNode = document.createElement("label");
|
||||
frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
|
||||
frameTitleNode.setAttribute("value", aTitle);
|
||||
container.appendChild(frameTitleNode);
|
||||
|
||||
frameDetails += SEARCH_LINE_FLAG + aLine;
|
||||
}
|
||||
|
||||
let frameDetailsNode = document.createElement("label");
|
||||
frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
|
||||
frameDetailsNode.setAttribute("value", frameDetails);
|
||||
container.appendChild(frameDetailsNode);
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
/**
|
||||
* Function called each time a stack frame item is removed.
|
||||
*
|
||||
* @param object aItem
|
||||
* The corresponding item.
|
||||
*/
|
||||
_onStackframeRemoved: function(aItem) {
|
||||
dumpn("Finalizing stackframe item: " + aItem.stringify());
|
||||
|
||||
// Remove the mirrored item in the classic list.
|
||||
let depth = aItem.attachment.depth;
|
||||
this._mirror.remove(this._mirror.getItemForAttachment(e => e.depth == depth));
|
||||
|
||||
// Forget the previously blackboxed stack frame url.
|
||||
this._prevBlackBoxedUrl = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for the stackframes container.
|
||||
*/
|
||||
_onSelect: function(e) {
|
||||
let stackframeItem = this.selectedItem;
|
||||
if (stackframeItem) {
|
||||
// The container is not empty and an actual item was selected.
|
||||
let depth = stackframeItem.attachment.depth;
|
||||
|
||||
// Mirror the selected item in the classic list.
|
||||
this.suppressSelectionEvents = true;
|
||||
this._mirror.selectedItem = e => e.attachment.depth == depth;
|
||||
this.suppressSelectionEvents = false;
|
||||
|
||||
DebuggerController.StackFrames.selectFrame(depth);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The scroll listener for the stackframes container.
|
||||
*/
|
||||
_onScroll: function() {
|
||||
// Update the stackframes container only if we have to.
|
||||
if (!this.dirty) {
|
||||
return;
|
||||
}
|
||||
// Allow requests to settle down first.
|
||||
setNamedTimeout("stack-scroll", STACK_FRAMES_SCROLL_DELAY, this._afterScroll);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests the addition of more frames from the controller.
|
||||
*/
|
||||
_afterScroll: function() {
|
||||
let scrollPosition = this.widget.getAttribute("scrollPosition");
|
||||
let scrollWidth = this.widget.getAttribute("scrollWidth");
|
||||
|
||||
// If the stackframes container scrolled almost to the end, with only
|
||||
// 1/10 of a breadcrumb remaining, load more content.
|
||||
if (scrollPosition - scrollWidth / 10 < 1) {
|
||||
this.ensureIndexIsVisible(CALL_STACK_PAGE_SIZE - 1);
|
||||
this.dirty = false;
|
||||
|
||||
// Loads more stack frames from the debugger server cache.
|
||||
DebuggerController.StackFrames.addMoreFrames();
|
||||
}
|
||||
},
|
||||
|
||||
_mirror: null,
|
||||
_prevBlackBoxedUrl: null
|
||||
});
|
||||
|
||||
DebuggerView.StackFrames = new StackFramesView(DebuggerController, DebuggerView);
|
203
browser/devtools/debugger/views/toolbar-view.js
Normal file
203
browser/devtools/debugger/views/toolbar-view.js
Normal file
@ -0,0 +1,203 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Functions handling the toolbar view: close button, expand/collapse button,
|
||||
* pause/resume and stepping buttons etc.
|
||||
*/
|
||||
function ToolbarView(DebuggerController, DebuggerView) {
|
||||
dumpn("ToolbarView was instantiated");
|
||||
|
||||
this.StackFrames = DebuggerController.StackFrames;
|
||||
this.ThreadState = DebuggerController.ThreadState;
|
||||
this.DebuggerController = DebuggerController;
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this);
|
||||
this._onResumePressed = this._onResumePressed.bind(this);
|
||||
this._onStepOverPressed = this._onStepOverPressed.bind(this);
|
||||
this._onStepInPressed = this._onStepInPressed.bind(this);
|
||||
this._onStepOutPressed = this._onStepOutPressed.bind(this);
|
||||
}
|
||||
|
||||
ToolbarView.prototype = {
|
||||
get activeThread() {
|
||||
return this.DebuggerController.activeThread;
|
||||
},
|
||||
|
||||
get resumptionWarnFunc() {
|
||||
return this.DebuggerController._ensureResumptionOrder;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the ToolbarView");
|
||||
|
||||
this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
|
||||
this._resumeButton = document.getElementById("resume");
|
||||
this._stepOverButton = document.getElementById("step-over");
|
||||
this._stepInButton = document.getElementById("step-in");
|
||||
this._stepOutButton = document.getElementById("step-out");
|
||||
this._resumeOrderTooltip = new Tooltip(document);
|
||||
this._resumeOrderTooltip.defaultPosition = TOOLBAR_ORDER_POPUP_POSITION;
|
||||
|
||||
let resumeKey = ShortcutUtils.prettifyShortcut(document.getElementById("resumeKey"));
|
||||
let stepOverKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOverKey"));
|
||||
let stepInKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepInKey"));
|
||||
let stepOutKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOutKey"));
|
||||
this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey);
|
||||
this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey);
|
||||
this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey);
|
||||
this._stepInTooltip = L10N.getFormatStr("stepInTooltip", stepInKey);
|
||||
this._stepOutTooltip = L10N.getFormatStr("stepOutTooltip", stepOutKey);
|
||||
|
||||
this._instrumentsPaneToggleButton.addEventListener("mousedown", this._onTogglePanesPressed, false);
|
||||
this._resumeButton.addEventListener("mousedown", this._onResumePressed, false);
|
||||
this._stepOverButton.addEventListener("mousedown", this._onStepOverPressed, false);
|
||||
this._stepInButton.addEventListener("mousedown", this._onStepInPressed, false);
|
||||
this._stepOutButton.addEventListener("mousedown", this._onStepOutPressed, false);
|
||||
|
||||
this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip);
|
||||
this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip);
|
||||
this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip);
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the ToolbarView");
|
||||
|
||||
this._instrumentsPaneToggleButton.removeEventListener("mousedown", this._onTogglePanesPressed, false);
|
||||
this._resumeButton.removeEventListener("mousedown", this._onResumePressed, false);
|
||||
this._stepOverButton.removeEventListener("mousedown", this._onStepOverPressed, false);
|
||||
this._stepInButton.removeEventListener("mousedown", this._onStepInPressed, false);
|
||||
this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
XULUtils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
resumeCommand: () => this._onResumePressed(),
|
||||
stepOverCommand: () => this._onStepOverPressed(),
|
||||
stepInCommand: () => this._onStepInPressed(),
|
||||
stepOutCommand: () => this._onStepOutPressed()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Display a warning when trying to resume a debuggee while another is paused.
|
||||
* Debuggees must be unpaused in a Last-In-First-Out order.
|
||||
*
|
||||
* @param string aPausedUrl
|
||||
* The URL of the last paused debuggee.
|
||||
*/
|
||||
showResumeWarning: function(aPausedUrl) {
|
||||
let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl);
|
||||
let defaultStyle = "default-tooltip-simple-text-colors";
|
||||
this._resumeOrderTooltip.setTextContent({ messages: [label], isAlertTooltip: true });
|
||||
this._resumeOrderTooltip.show(this._resumeButton);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the resume button state based on the debugger active thread.
|
||||
*
|
||||
* @param string aState
|
||||
* Either "paused" or "attached".
|
||||
*/
|
||||
toggleResumeButtonState: function(aState) {
|
||||
// If we're paused, check and show a resume label on the button.
|
||||
if (aState == "paused") {
|
||||
this._resumeButton.setAttribute("checked", "true");
|
||||
this._resumeButton.setAttribute("tooltiptext", this._resumeTooltip);
|
||||
}
|
||||
// If we're attached, do the opposite.
|
||||
else if (aState == "attached") {
|
||||
this._resumeButton.removeAttribute("checked");
|
||||
this._resumeButton.setAttribute("tooltiptext", this._pauseTooltip);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the toggle button click event.
|
||||
*/
|
||||
_onTogglePanesPressed: function() {
|
||||
DebuggerView.toggleInstrumentsPane({
|
||||
visible: DebuggerView.instrumentsPaneHidden,
|
||||
animated: true,
|
||||
delayed: true
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the pause/resume button click event.
|
||||
*/
|
||||
_onResumePressed: function() {
|
||||
if (this.StackFrames._currentFrameDescription != FRAME_TYPE.NORMAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeThread.paused) {
|
||||
this.StackFrames.currentFrameDepth = -1;
|
||||
this.activeThread.resume(this.resumptionWarnFunc);
|
||||
} else {
|
||||
this.ThreadState.interruptedByResumeButton = true;
|
||||
this.activeThread.interrupt();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the step over button click event.
|
||||
*/
|
||||
_onStepOverPressed: function() {
|
||||
if (this.activeThread.paused) {
|
||||
this.StackFrames.currentFrameDepth = -1;
|
||||
this.activeThread.stepOver(this.resumptionWarnFunc);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the step in button click event.
|
||||
*/
|
||||
_onStepInPressed: function() {
|
||||
if (this.StackFrames._currentFrameDescription != FRAME_TYPE.NORMAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeThread.paused) {
|
||||
this.StackFrames.currentFrameDepth = -1;
|
||||
this.activeThread.stepIn(this.resumptionWarnFunc);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the step out button click event.
|
||||
*/
|
||||
_onStepOutPressed: function() {
|
||||
if (this.activeThread.paused) {
|
||||
this.StackFrames.currentFrameDepth = -1;
|
||||
this.activeThread.stepOut(this.resumptionWarnFunc);
|
||||
}
|
||||
},
|
||||
|
||||
_instrumentsPaneToggleButton: null,
|
||||
_resumeButton: null,
|
||||
_stepOverButton: null,
|
||||
_stepInButton: null,
|
||||
_stepOutButton: null,
|
||||
_resumeOrderTooltip: null,
|
||||
_resumeTooltip: "",
|
||||
_pauseTooltip: "",
|
||||
_stepOverTooltip: "",
|
||||
_stepInTooltip: "",
|
||||
_stepOutTooltip: ""
|
||||
};
|
||||
|
||||
DebuggerView.Toolbar = new ToolbarView(DebuggerController, DebuggerView);
|
416
browser/devtools/debugger/views/tracer-view.js
Normal file
416
browser/devtools/debugger/views/tracer-view.js
Normal file
@ -0,0 +1,416 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Functions handling the traces UI.
|
||||
*/
|
||||
function TracerView(DebuggerController, DebuggerView) {
|
||||
this._selectedItem = null;
|
||||
this._matchingItems = null;
|
||||
this.widget = null;
|
||||
|
||||
this.Tracer = DebuggerController.Tracer;
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this._highlightItem = this._highlightItem.bind(this);
|
||||
this._isNotSelectedItem = this._isNotSelectedItem.bind(this);
|
||||
|
||||
this._unhighlightMatchingItems =
|
||||
DevToolsUtils.makeInfallible(this._unhighlightMatchingItems.bind(this));
|
||||
this._onToggleTracing =
|
||||
DevToolsUtils.makeInfallible(this._onToggleTracing.bind(this));
|
||||
this._onStartTracing =
|
||||
DevToolsUtils.makeInfallible(this._onStartTracing.bind(this));
|
||||
this._onClear =
|
||||
DevToolsUtils.makeInfallible(this._onClear.bind(this));
|
||||
this._onSelect =
|
||||
DevToolsUtils.makeInfallible(this._onSelect.bind(this));
|
||||
this._onMouseOver =
|
||||
DevToolsUtils.makeInfallible(this._onMouseOver.bind(this));
|
||||
this._onSearch =
|
||||
DevToolsUtils.makeInfallible(this._onSearch.bind(this));
|
||||
}
|
||||
|
||||
TracerView.MAX_TRACES = 200;
|
||||
|
||||
TracerView.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the TracerView");
|
||||
|
||||
this._traceButton = document.getElementById("trace");
|
||||
this._tracerTab = document.getElementById("tracer-tab");
|
||||
|
||||
// Remove tracer related elements from the dom and tear everything down if
|
||||
// the tracer isn't enabled.
|
||||
if (!Prefs.tracerEnabled) {
|
||||
this._traceButton.remove();
|
||||
this._traceButton = null;
|
||||
this._tracerTab.remove();
|
||||
this._tracerTab = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.widget = new FastListWidget(document.getElementById("tracer-traces"));
|
||||
this._traceButton.removeAttribute("hidden");
|
||||
this._tracerTab.removeAttribute("hidden");
|
||||
|
||||
this._search = document.getElementById("tracer-search");
|
||||
this._template = document.getElementsByClassName("trace-item-template")[0];
|
||||
this._templateItem = this._template.getElementsByClassName("trace-item")[0];
|
||||
this._templateTypeIcon = this._template.getElementsByClassName("trace-type")[0];
|
||||
this._templateNameNode = this._template.getElementsByClassName("trace-name")[0];
|
||||
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
this.widget.addEventListener("mouseover", this._onMouseOver, false);
|
||||
this.widget.addEventListener("mouseout", this._unhighlightMatchingItems, false);
|
||||
this._search.addEventListener("input", this._onSearch, false);
|
||||
|
||||
this._startTooltip = L10N.getStr("startTracingTooltip");
|
||||
this._stopTooltip = L10N.getStr("stopTracingTooltip");
|
||||
this._tracingNotStartedString = L10N.getStr("tracingNotStartedText");
|
||||
this._noFunctionCallsString = L10N.getStr("noFunctionCallsText");
|
||||
|
||||
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
|
||||
this.emptyText = this._tracingNotStartedString;
|
||||
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the TracerView");
|
||||
|
||||
if (!this.widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
this.widget.removeEventListener("mouseover", this._onMouseOver, false);
|
||||
this.widget.removeEventListener("mouseout", this._unhighlightMatchingItems, false);
|
||||
this._search.removeEventListener("input", this._onSearch, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
XULUtils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
toggleTracing: () => this._onToggleTracing(),
|
||||
startTracing: () => this._onStartTracing(),
|
||||
clearTraces: () => this._onClear()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked by the "toggleTracing" command to switch the tracer state.
|
||||
*/
|
||||
_onToggleTracing: function() {
|
||||
if (this.Tracer.tracing) {
|
||||
this._onStopTracing();
|
||||
} else {
|
||||
this._onStartTracing();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked either by the "startTracing" command or by
|
||||
* _onToggleTracing to start execution tracing in the backend.
|
||||
*
|
||||
* @return object
|
||||
* A promise resolved once the tracing has successfully started.
|
||||
*/
|
||||
_onStartTracing: function() {
|
||||
this._traceButton.setAttribute("checked", true);
|
||||
this._traceButton.setAttribute("tooltiptext", this._stopTooltip);
|
||||
|
||||
this.empty();
|
||||
this.emptyText = this._noFunctionCallsString;
|
||||
|
||||
let deferred = promise.defer();
|
||||
this.Tracer.startTracing(deferred.resolve);
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked by _onToggleTracing to stop execution tracing in the
|
||||
* backend.
|
||||
*
|
||||
* @return object
|
||||
* A promise resolved once the tracing has successfully stopped.
|
||||
*/
|
||||
_onStopTracing: function() {
|
||||
this._traceButton.removeAttribute("checked");
|
||||
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
|
||||
|
||||
this.emptyText = this._tracingNotStartedString;
|
||||
|
||||
let deferred = promise.defer();
|
||||
this.Tracer.stopTracing(deferred.resolve);
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked by the "clearTraces" command to empty the traces pane.
|
||||
*/
|
||||
_onClear: function() {
|
||||
this.empty();
|
||||
},
|
||||
|
||||
/**
|
||||
* Populate the given parent scope with the variable with the provided name
|
||||
* and value.
|
||||
*
|
||||
* @param String aName
|
||||
* The name of the variable.
|
||||
* @param Object aParent
|
||||
* The parent scope.
|
||||
* @param Object aValue
|
||||
* The value of the variable.
|
||||
*/
|
||||
_populateVariable: function(aName, aParent, aValue) {
|
||||
let item = aParent.addItem(aName, { value: aValue });
|
||||
|
||||
if (aValue) {
|
||||
let wrappedValue = new this.Tracer.WrappedObject(aValue);
|
||||
this.DebuggerView.Variables.controller.populate(item, wrappedValue);
|
||||
item.expand();
|
||||
item.twisty = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the widget's "select" event. Displays parameters, exception, or
|
||||
* return value depending on whether the selected trace is a call, throw, or
|
||||
* return respectively.
|
||||
*
|
||||
* @param Object traceItem
|
||||
* The selected trace item.
|
||||
*/
|
||||
_onSelect: function _onSelect({ detail: traceItem }) {
|
||||
if (!traceItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = traceItem.attachment.trace;
|
||||
const { location: { url, line } } = data;
|
||||
this.DebuggerView.setEditorLocation(
|
||||
this.DebuggerView.Sources.getActorForLocation({ url }),
|
||||
line,
|
||||
{ noDebug: true }
|
||||
);
|
||||
|
||||
this.DebuggerView.Variables.empty();
|
||||
const scope = this.DebuggerView.Variables.addScope();
|
||||
|
||||
if (data.type == "call") {
|
||||
const params = DevToolsUtils.zip(data.parameterNames, data.arguments);
|
||||
for (let [name, val] of params) {
|
||||
if (val === undefined) {
|
||||
scope.addItem(name, { value: "<value not available>" });
|
||||
} else {
|
||||
this._populateVariable(name, scope, val);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const varName = "<" + (data.type == "throw" ? "exception" : data.type) + ">";
|
||||
this._populateVariable(varName, scope, data.returnVal);
|
||||
}
|
||||
|
||||
scope.expand();
|
||||
this.DebuggerView.showInstrumentsPane();
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the hover frame enter/exit highlighting to a given item.
|
||||
*/
|
||||
_highlightItem: function(aItem) {
|
||||
if (!aItem || !aItem.target) {
|
||||
return;
|
||||
}
|
||||
const trace = aItem.target.querySelector(".trace-item");
|
||||
trace.classList.add("selected-matching");
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the hover frame enter/exit highlighting to a given item.
|
||||
*/
|
||||
_unhighlightItem: function(aItem) {
|
||||
if (!aItem || !aItem.target) {
|
||||
return;
|
||||
}
|
||||
const match = aItem.target.querySelector(".selected-matching");
|
||||
if (match) {
|
||||
match.classList.remove("selected-matching");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the frame enter/exit pair highlighting we do when hovering.
|
||||
*/
|
||||
_unhighlightMatchingItems: function() {
|
||||
if (this._matchingItems) {
|
||||
this._matchingItems.forEach(this._unhighlightItem);
|
||||
this._matchingItems = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the given item is not the selected item.
|
||||
*/
|
||||
_isNotSelectedItem: function(aItem) {
|
||||
return aItem !== this.selectedItem;
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight the frame enter/exit pair of items for the given item.
|
||||
*/
|
||||
_highlightMatchingItems: function(aItem) {
|
||||
const frameId = aItem.attachment.trace.frameId;
|
||||
const predicate = e => e.attachment.trace.frameId == frameId;
|
||||
|
||||
this._unhighlightMatchingItems();
|
||||
this._matchingItems = this.items.filter(predicate);
|
||||
this._matchingItems
|
||||
.filter(this._isNotSelectedItem)
|
||||
.forEach(this._highlightItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for the mouseover event.
|
||||
*/
|
||||
_onMouseOver: function({ target }) {
|
||||
const traceItem = this.getItemForElement(target);
|
||||
if (traceItem) {
|
||||
this._highlightMatchingItems(traceItem);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for typing in the search box.
|
||||
*/
|
||||
_onSearch: function() {
|
||||
const query = this._search.value.trim().toLowerCase();
|
||||
const predicate = name => name.toLowerCase().contains(query);
|
||||
this.filterContents(item => predicate(item.attachment.trace.name));
|
||||
},
|
||||
|
||||
/**
|
||||
* Select the traces tab in the sidebar.
|
||||
*/
|
||||
selectTab: function() {
|
||||
const tabs = this._tracerTab.parentElement;
|
||||
tabs.selectedIndex = Array.indexOf(tabs.children, this._tracerTab);
|
||||
},
|
||||
|
||||
/**
|
||||
* Commit all staged items to the widget. Overridden so that we can call
|
||||
* |FastListWidget.prototype.flush|.
|
||||
*/
|
||||
commit: function() {
|
||||
WidgetMethods.commit.call(this);
|
||||
// TODO: Accessing non-standard widget properties. Figure out what's the
|
||||
// best way to expose such things. Bug 895514.
|
||||
this.widget.flush();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the trace record provided as an argument to the view.
|
||||
*
|
||||
* @param object aTrace
|
||||
* The trace record coming from the tracer actor.
|
||||
*/
|
||||
addTrace: function(aTrace) {
|
||||
// Create the element node for the trace item.
|
||||
let view = this._createView(aTrace);
|
||||
|
||||
// Append a source item to this container.
|
||||
this.push([view], {
|
||||
staged: true,
|
||||
attachment: {
|
||||
trace: aTrace
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Customization function for creating an item's UI.
|
||||
*
|
||||
* @return nsIDOMNode
|
||||
* The network request view.
|
||||
*/
|
||||
_createView: function(aTrace) {
|
||||
let { type, name, location, blackBoxed, depth, frameId } = aTrace;
|
||||
let { parameterNames, returnVal, arguments: args } = aTrace;
|
||||
let fragment = document.createDocumentFragment();
|
||||
|
||||
this._templateItem.classList.toggle("black-boxed", blackBoxed);
|
||||
this._templateItem.setAttribute("tooltiptext", SourceUtils.trimUrl(location.url));
|
||||
this._templateItem.style.MozPaddingStart = depth + "em";
|
||||
|
||||
const TYPES = ["call", "yield", "return", "throw"];
|
||||
for (let t of TYPES) {
|
||||
this._templateTypeIcon.classList.toggle("trace-" + t, t == type);
|
||||
}
|
||||
this._templateTypeIcon.setAttribute("value", {
|
||||
call: "\u2192",
|
||||
yield: "Y",
|
||||
return: "\u2190",
|
||||
throw: "E",
|
||||
terminated: "TERMINATED"
|
||||
}[type]);
|
||||
|
||||
this._templateNameNode.setAttribute("value", name);
|
||||
|
||||
// All extra syntax and parameter nodes added.
|
||||
const addedNodes = [];
|
||||
|
||||
if (parameterNames) {
|
||||
const syntax = (p) => {
|
||||
const el = document.createElement("label");
|
||||
el.setAttribute("value", p);
|
||||
el.classList.add("trace-syntax");
|
||||
el.classList.add("plain");
|
||||
addedNodes.push(el);
|
||||
return el;
|
||||
};
|
||||
|
||||
this._templateItem.appendChild(syntax("("));
|
||||
|
||||
for (let i = 0, n = parameterNames.length; i < n; i++) {
|
||||
let param = document.createElement("label");
|
||||
param.setAttribute("value", parameterNames[i]);
|
||||
param.classList.add("trace-param");
|
||||
param.classList.add("plain");
|
||||
addedNodes.push(param);
|
||||
this._templateItem.appendChild(param);
|
||||
|
||||
if (i + 1 !== n) {
|
||||
this._templateItem.appendChild(syntax(", "));
|
||||
}
|
||||
}
|
||||
|
||||
this._templateItem.appendChild(syntax(")"));
|
||||
}
|
||||
|
||||
// Flatten the DOM by removing one redundant box (the template container).
|
||||
for (let node of this._template.childNodes) {
|
||||
fragment.appendChild(node.cloneNode(true));
|
||||
}
|
||||
|
||||
// Remove any added nodes from the template.
|
||||
for (let node of addedNodes) {
|
||||
this._templateItem.removeChild(node);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
});
|
||||
|
||||
DebuggerView.Tracer = new TracerView(DebuggerController, DebuggerView);
|
300
browser/devtools/debugger/views/variable-bubble-view.js
Normal file
300
browser/devtools/debugger/views/variable-bubble-view.js
Normal file
@ -0,0 +1,300 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Functions handling the variables bubble UI.
|
||||
*/
|
||||
function VariableBubbleView(DebuggerController, DebuggerView) {
|
||||
dumpn("VariableBubbleView was instantiated");
|
||||
|
||||
this.StackFrames = DebuggerController.StackFrames;
|
||||
this.Parser = DebuggerController.Parser;
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this._onMouseMove = this._onMouseMove.bind(this);
|
||||
this._onMouseOut = this._onMouseOut.bind(this);
|
||||
this._onPopupHiding = this._onPopupHiding.bind(this);
|
||||
}
|
||||
|
||||
VariableBubbleView.prototype = {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the VariableBubbleView");
|
||||
|
||||
this._toolbox = DebuggerController._toolbox;
|
||||
this._editorContainer = document.getElementById("editor");
|
||||
this._editorContainer.addEventListener("mousemove", this._onMouseMove, false);
|
||||
this._editorContainer.addEventListener("mouseout", this._onMouseOut, false);
|
||||
|
||||
this._tooltip = new Tooltip(document, {
|
||||
closeOnEvents: [{
|
||||
emitter: this._toolbox,
|
||||
event: "select"
|
||||
}, {
|
||||
emitter: this._editorContainer,
|
||||
event: "scroll",
|
||||
useCapture: true
|
||||
}]
|
||||
});
|
||||
this._tooltip.defaultPosition = EDITOR_VARIABLE_POPUP_POSITION;
|
||||
this._tooltip.defaultShowDelay = EDITOR_VARIABLE_HOVER_DELAY;
|
||||
this._tooltip.panel.addEventListener("popuphiding", this._onPopupHiding);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the VariableBubbleView");
|
||||
|
||||
this._tooltip.panel.removeEventListener("popuphiding", this._onPopupHiding);
|
||||
this._editorContainer.removeEventListener("mousemove", this._onMouseMove, false);
|
||||
this._editorContainer.removeEventListener("mouseout", this._onMouseOut, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies whether literals can be (redundantly) inspected in a popup.
|
||||
* This behavior is deprecated, but still tested in a few places.
|
||||
*/
|
||||
_ignoreLiterals: true,
|
||||
|
||||
/**
|
||||
* Searches for an identifier underneath the specified position in the
|
||||
* source editor, and if found, opens a VariablesView inspection popup.
|
||||
*
|
||||
* @param number x, y
|
||||
* The left/top coordinates where to look for an identifier.
|
||||
*/
|
||||
_findIdentifier: function(x, y) {
|
||||
let editor = this.DebuggerView.editor;
|
||||
|
||||
// Calculate the editor's line and column at the current x and y coords.
|
||||
let hoveredPos = editor.getPositionFromCoords({ left: x, top: y });
|
||||
let hoveredOffset = editor.getOffset(hoveredPos);
|
||||
let hoveredLine = hoveredPos.line;
|
||||
let hoveredColumn = hoveredPos.ch;
|
||||
|
||||
// A source contains multiple scripts. Find the start index of the script
|
||||
// containing the specified offset relative to its parent source.
|
||||
let contents = editor.getText();
|
||||
let location = this.DebuggerView.Sources.selectedValue;
|
||||
let parsedSource = this.Parser.get(contents, location);
|
||||
let scriptInfo = parsedSource.getScriptInfo(hoveredOffset);
|
||||
|
||||
// If the script length is negative, we're not hovering JS source code.
|
||||
if (scriptInfo.length == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Using the script offset, determine the actual line and column inside the
|
||||
// script, to use when finding identifiers.
|
||||
let scriptStart = editor.getPosition(scriptInfo.start);
|
||||
let scriptLineOffset = scriptStart.line;
|
||||
let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
|
||||
|
||||
let scriptLine = hoveredLine - scriptLineOffset;
|
||||
let scriptColumn = hoveredColumn - scriptColumnOffset;
|
||||
let identifierInfo = parsedSource.getIdentifierAt({
|
||||
line: scriptLine + 1,
|
||||
column: scriptColumn,
|
||||
scriptIndex: scriptInfo.index,
|
||||
ignoreLiterals: this._ignoreLiterals
|
||||
});
|
||||
|
||||
// If the info is null, we're not hovering any identifier.
|
||||
if (!identifierInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Transform the line and column relative to the parsed script back
|
||||
// to the context of the parent source.
|
||||
let { start: identifierStart, end: identifierEnd } = identifierInfo.location;
|
||||
let identifierCoords = {
|
||||
line: identifierStart.line + scriptLineOffset,
|
||||
column: identifierStart.column + scriptColumnOffset,
|
||||
length: identifierEnd.column - identifierStart.column
|
||||
};
|
||||
|
||||
// Evaluate the identifier in the current stack frame and show the
|
||||
// results in a VariablesView inspection popup.
|
||||
this.StackFrames.evaluate(identifierInfo.evalString)
|
||||
.then(frameFinished => {
|
||||
if ("return" in frameFinished) {
|
||||
this.showContents({
|
||||
coords: identifierCoords,
|
||||
evalPrefix: identifierInfo.evalString,
|
||||
objectActor: frameFinished.return
|
||||
});
|
||||
} else {
|
||||
let msg = "Evaluation has thrown for: " + identifierInfo.evalString;
|
||||
console.warn(msg);
|
||||
dumpn(msg);
|
||||
}
|
||||
})
|
||||
.then(null, err => {
|
||||
let msg = "Couldn't evaluate: " + err.message;
|
||||
console.error(msg);
|
||||
dumpn(msg);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows an inspection popup for a specified object actor grip.
|
||||
*
|
||||
* @param string object
|
||||
* An object containing the following properties:
|
||||
* - coords: the inspected identifier coordinates in the editor,
|
||||
* containing the { line, column, length } properties.
|
||||
* - evalPrefix: a prefix for the variables view evaluation macros.
|
||||
* - objectActor: the value grip for the object actor.
|
||||
*/
|
||||
showContents: function({ coords, evalPrefix, objectActor }) {
|
||||
let editor = this.DebuggerView.editor;
|
||||
let { line, column, length } = coords;
|
||||
|
||||
// Highlight the function found at the mouse position.
|
||||
this._markedText = editor.markText(
|
||||
{ line: line - 1, ch: column },
|
||||
{ line: line - 1, ch: column + length });
|
||||
|
||||
// If the grip represents a primitive value, use a more lightweight
|
||||
// machinery to display it.
|
||||
if (VariablesView.isPrimitive({ value: objectActor })) {
|
||||
let className = VariablesView.getClass(objectActor);
|
||||
let textContent = VariablesView.getString(objectActor);
|
||||
this._tooltip.setTextContent({
|
||||
messages: [textContent],
|
||||
messagesClass: className,
|
||||
containerClass: "plain"
|
||||
}, [{
|
||||
label: L10N.getStr('addWatchExpressionButton'),
|
||||
className: "dbg-expression-button",
|
||||
command: () => {
|
||||
this.DebuggerView.VariableBubble.hideContents();
|
||||
this.DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
|
||||
}
|
||||
}]);
|
||||
} else {
|
||||
this._tooltip.setVariableContent(objectActor, {
|
||||
searchPlaceholder: L10N.getStr("emptyPropertiesFilterText"),
|
||||
searchEnabled: Prefs.variablesSearchboxVisible,
|
||||
eval: (variable, value) => {
|
||||
let string = variable.evaluationMacro(variable, value);
|
||||
this.StackFrames.evaluate(string);
|
||||
this.DebuggerView.VariableBubble.hideContents();
|
||||
}
|
||||
}, {
|
||||
getEnvironmentClient: aObject => gThreadClient.environment(aObject),
|
||||
getObjectClient: aObject => gThreadClient.pauseGrip(aObject),
|
||||
simpleValueEvalMacro: this._getSimpleValueEvalMacro(evalPrefix),
|
||||
getterOrSetterEvalMacro: this._getGetterOrSetterEvalMacro(evalPrefix),
|
||||
overrideValueEvalMacro: this._getOverrideValueEvalMacro(evalPrefix)
|
||||
}, {
|
||||
fetched: (aEvent, aType) => {
|
||||
if (aType == "properties") {
|
||||
window.emit(EVENTS.FETCHED_BUBBLE_PROPERTIES);
|
||||
}
|
||||
}
|
||||
}, [{
|
||||
label: L10N.getStr("addWatchExpressionButton"),
|
||||
className: "dbg-expression-button",
|
||||
command: () => {
|
||||
this.DebuggerView.VariableBubble.hideContents();
|
||||
this.DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
|
||||
}
|
||||
}], this._toolbox);
|
||||
}
|
||||
|
||||
this._tooltip.show(this._markedText.anchor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the inspection popup.
|
||||
*/
|
||||
hideContents: function() {
|
||||
clearNamedTimeout("editor-mouse-move");
|
||||
this._tooltip.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the inspection popup is shown.
|
||||
*
|
||||
* @return boolean
|
||||
* True if the panel is shown or showing, false otherwise.
|
||||
*/
|
||||
contentsShown: function() {
|
||||
return this._tooltip.isShown();
|
||||
},
|
||||
|
||||
/**
|
||||
* Functions for getting customized variables view evaluation macros.
|
||||
*
|
||||
* @param string aPrefix
|
||||
* See the corresponding VariablesView.* functions.
|
||||
*/
|
||||
_getSimpleValueEvalMacro: function(aPrefix) {
|
||||
return (item, string) =>
|
||||
VariablesView.simpleValueEvalMacro(item, string, aPrefix);
|
||||
},
|
||||
_getGetterOrSetterEvalMacro: function(aPrefix) {
|
||||
return (item, string) =>
|
||||
VariablesView.getterOrSetterEvalMacro(item, string, aPrefix);
|
||||
},
|
||||
_getOverrideValueEvalMacro: function(aPrefix) {
|
||||
return (item, string) =>
|
||||
VariablesView.overrideValueEvalMacro(item, string, aPrefix);
|
||||
},
|
||||
|
||||
/**
|
||||
* The mousemove listener for the source editor.
|
||||
*/
|
||||
_onMouseMove: function(e) {
|
||||
// Prevent the variable inspection popup from showing when the thread client
|
||||
// is not paused, or while a popup is already visible, or when the user tries
|
||||
// to select text in the editor.
|
||||
let isResumed = gThreadClient && gThreadClient.state != "paused";
|
||||
let isSelecting = this.DebuggerView.editor.somethingSelected() && e.buttons > 0;
|
||||
let isPopupVisible = !this._tooltip.isHidden();
|
||||
if (isResumed || isSelecting || isPopupVisible) {
|
||||
clearNamedTimeout("editor-mouse-move");
|
||||
return;
|
||||
}
|
||||
// Allow events to settle down first. If the mouse hovers over
|
||||
// a certain point in the editor long enough, try showing a variable bubble.
|
||||
setNamedTimeout("editor-mouse-move",
|
||||
EDITOR_VARIABLE_HOVER_DELAY, () => this._findIdentifier(e.clientX, e.clientY));
|
||||
},
|
||||
|
||||
/**
|
||||
* The mouseout listener for the source editor container node.
|
||||
*/
|
||||
_onMouseOut: function() {
|
||||
clearNamedTimeout("editor-mouse-move");
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the popup hiding event.
|
||||
*/
|
||||
_onPopupHiding: function({ target }) {
|
||||
if (this._tooltip.panel != target) {
|
||||
return;
|
||||
}
|
||||
if (this._markedText) {
|
||||
this._markedText.clear();
|
||||
this._markedText = null;
|
||||
}
|
||||
if (!this._tooltip.isEmpty()) {
|
||||
this._tooltip.empty();
|
||||
}
|
||||
},
|
||||
|
||||
_editorContainer: null,
|
||||
_markedText: null,
|
||||
_tooltip: null
|
||||
};
|
||||
|
||||
DebuggerView.VariableBubble = new VariableBubbleView(DebuggerController, DebuggerView);
|
295
browser/devtools/debugger/views/watch-expressions-view.js
Normal file
295
browser/devtools/debugger/views/watch-expressions-view.js
Normal file
@ -0,0 +1,295 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Functions handling the watch expressions UI.
|
||||
*/
|
||||
function WatchExpressionsView(DebuggerController, DebuggerView) {
|
||||
dumpn("WatchExpressionsView was instantiated");
|
||||
|
||||
this.StackFrames = DebuggerController.StackFrames;
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this.switchExpression = this.switchExpression.bind(this);
|
||||
this.deleteExpression = this.deleteExpression.bind(this);
|
||||
this._createItemView = this._createItemView.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onClose = this._onClose.bind(this);
|
||||
this._onBlur = this._onBlur.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the WatchExpressionsView");
|
||||
|
||||
this.widget = new SimpleListWidget(document.getElementById("expressions"));
|
||||
this.widget.setAttribute("context", "debuggerWatchExpressionsContextMenu");
|
||||
this.widget.addEventListener("click", this._onClick, false);
|
||||
|
||||
this.headerText = L10N.getStr("addWatchExpressionText");
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the WatchExpressionsView");
|
||||
|
||||
this.widget.removeEventListener("click", this._onClick, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
XULUtils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
addWatchExpressionCommand: () => this._onCmdAddExpression(),
|
||||
removeAllWatchExpressionsCommand: () => this._onCmdRemoveAllExpressions()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a watch expression in this container.
|
||||
*
|
||||
* @param string aExpression [optional]
|
||||
* An optional initial watch expression text.
|
||||
* @param boolean aSkipUserInput [optional]
|
||||
* Pass true to avoid waiting for additional user input
|
||||
* on the watch expression.
|
||||
*/
|
||||
addExpression: function(aExpression = "", aSkipUserInput = false) {
|
||||
// Watch expressions are UI elements which benefit from visible panes.
|
||||
this.DebuggerView.showInstrumentsPane();
|
||||
|
||||
// Create the element node for the watch expression item.
|
||||
let itemView = this._createItemView(aExpression);
|
||||
|
||||
// Append a watch expression item to this container.
|
||||
let expressionItem = this.push([itemView.container], {
|
||||
index: 0, /* specifies on which position should the item be appended */
|
||||
attachment: {
|
||||
view: itemView,
|
||||
initialExpression: aExpression,
|
||||
currentExpression: "",
|
||||
}
|
||||
});
|
||||
|
||||
// Automatically focus the new watch expression input
|
||||
// if additional user input is desired.
|
||||
if (!aSkipUserInput) {
|
||||
expressionItem.attachment.view.inputNode.select();
|
||||
expressionItem.attachment.view.inputNode.focus();
|
||||
this.DebuggerView.Variables.parentNode.scrollTop = 0;
|
||||
}
|
||||
// Otherwise, add and evaluate the new watch expression immediately.
|
||||
else {
|
||||
this.toggleContents(false);
|
||||
this._onBlur({ target: expressionItem.attachment.view.inputNode });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the watch expression corresponding to the specified variable item.
|
||||
* This function is called whenever a watch expression's code is edited in
|
||||
* the variables view container.
|
||||
*
|
||||
* @param Variable aVar
|
||||
* The variable representing the watch expression evaluation.
|
||||
* @param string aExpression
|
||||
* The new watch expression text.
|
||||
*/
|
||||
switchExpression: function(aVar, aExpression) {
|
||||
let expressionItem =
|
||||
[i for (i of this) if (i.attachment.currentExpression == aVar.name)][0];
|
||||
|
||||
// Remove the watch expression if it's going to be empty or a duplicate.
|
||||
if (!aExpression || this.getAllStrings().indexOf(aExpression) != -1) {
|
||||
this.deleteExpression(aVar);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the watch expression code string.
|
||||
expressionItem.attachment.currentExpression = aExpression;
|
||||
expressionItem.attachment.view.inputNode.value = aExpression;
|
||||
|
||||
// Synchronize with the controller's watch expressions store.
|
||||
this.StackFrames.syncWatchExpressions();
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the watch expression corresponding to the specified variable item.
|
||||
* This function is called whenever a watch expression's value is edited in
|
||||
* the variables view container.
|
||||
*
|
||||
* @param Variable aVar
|
||||
* The variable representing the watch expression evaluation.
|
||||
*/
|
||||
deleteExpression: function(aVar) {
|
||||
let expressionItem =
|
||||
[i for (i of this) if (i.attachment.currentExpression == aVar.name)][0];
|
||||
|
||||
// Remove the watch expression.
|
||||
this.remove(expressionItem);
|
||||
|
||||
// Synchronize with the controller's watch expressions store.
|
||||
this.StackFrames.syncWatchExpressions();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the watch expression code string for an item in this container.
|
||||
*
|
||||
* @param number aIndex
|
||||
* The index used to identify the watch expression.
|
||||
* @return string
|
||||
* The watch expression code string.
|
||||
*/
|
||||
getString: function(aIndex) {
|
||||
return this.getItemAtIndex(aIndex).attachment.currentExpression;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the watch expressions code strings for all items in this container.
|
||||
*
|
||||
* @return array
|
||||
* The watch expressions code strings.
|
||||
*/
|
||||
getAllStrings: function() {
|
||||
return this.items.map(e => e.attachment.currentExpression);
|
||||
},
|
||||
|
||||
/**
|
||||
* Customization function for creating an item's UI.
|
||||
*
|
||||
* @param string aExpression
|
||||
* The watch expression string.
|
||||
*/
|
||||
_createItemView: function(aExpression) {
|
||||
let container = document.createElement("hbox");
|
||||
container.className = "list-widget-item dbg-expression";
|
||||
container.setAttribute("align", "center");
|
||||
|
||||
let arrowNode = document.createElement("hbox");
|
||||
arrowNode.className = "dbg-expression-arrow";
|
||||
|
||||
let inputNode = document.createElement("textbox");
|
||||
inputNode.className = "plain dbg-expression-input devtools-monospace";
|
||||
inputNode.setAttribute("value", aExpression);
|
||||
inputNode.setAttribute("flex", "1");
|
||||
|
||||
let closeNode = document.createElement("toolbarbutton");
|
||||
closeNode.className = "plain variables-view-delete";
|
||||
|
||||
closeNode.addEventListener("click", this._onClose, false);
|
||||
inputNode.addEventListener("blur", this._onBlur, false);
|
||||
inputNode.addEventListener("keypress", this._onKeyPress, false);
|
||||
|
||||
container.appendChild(arrowNode);
|
||||
container.appendChild(inputNode);
|
||||
container.appendChild(closeNode);
|
||||
|
||||
return {
|
||||
container: container,
|
||||
arrowNode: arrowNode,
|
||||
inputNode: inputNode,
|
||||
closeNode: closeNode
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the add watch expression key sequence was pressed.
|
||||
*/
|
||||
_onCmdAddExpression: function(aText) {
|
||||
// Only add a new expression if there's no pending input.
|
||||
if (this.getAllStrings().indexOf("") == -1) {
|
||||
this.addExpression(aText || this.DebuggerView.editor.getSelection());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the remove all watch expressions key sequence was pressed.
|
||||
*/
|
||||
_onCmdRemoveAllExpressions: function() {
|
||||
// Empty the view of all the watch expressions and clear the cache.
|
||||
this.empty();
|
||||
|
||||
// Synchronize with the controller's watch expressions store.
|
||||
this.StackFrames.syncWatchExpressions();
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for this container.
|
||||
*/
|
||||
_onClick: function(e) {
|
||||
if (e.button != 0) {
|
||||
// Only allow left-click to trigger this event.
|
||||
return;
|
||||
}
|
||||
let expressionItem = this.getItemForElement(e.target);
|
||||
if (!expressionItem) {
|
||||
// The container is empty or we didn't click on an actual item.
|
||||
this.addExpression();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for a watch expression's close button.
|
||||
*/
|
||||
_onClose: function(e) {
|
||||
// Remove the watch expression.
|
||||
this.remove(this.getItemForElement(e.target));
|
||||
|
||||
// Synchronize with the controller's watch expressions store.
|
||||
this.StackFrames.syncWatchExpressions();
|
||||
|
||||
// Prevent clicking the expression element itself.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* The blur listener for a watch expression's textbox.
|
||||
*/
|
||||
_onBlur: function({ target: textbox }) {
|
||||
let expressionItem = this.getItemForElement(textbox);
|
||||
let oldExpression = expressionItem.attachment.currentExpression;
|
||||
let newExpression = textbox.value.trim();
|
||||
|
||||
// Remove the watch expression if it's empty.
|
||||
if (!newExpression) {
|
||||
this.remove(expressionItem);
|
||||
}
|
||||
// Remove the watch expression if it's a duplicate.
|
||||
else if (!oldExpression && this.getAllStrings().indexOf(newExpression) != -1) {
|
||||
this.remove(expressionItem);
|
||||
}
|
||||
// Expression is eligible.
|
||||
else {
|
||||
expressionItem.attachment.currentExpression = newExpression;
|
||||
}
|
||||
|
||||
// Synchronize with the controller's watch expressions store.
|
||||
this.StackFrames.syncWatchExpressions();
|
||||
},
|
||||
|
||||
/**
|
||||
* The keypress listener for a watch expression's textbox.
|
||||
*/
|
||||
_onKeyPress: function(e) {
|
||||
switch (e.keyCode) {
|
||||
case e.DOM_VK_RETURN:
|
||||
case e.DOM_VK_ESCAPE:
|
||||
e.stopPropagation();
|
||||
this.DebuggerView.editor.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
DebuggerView.WatchExpressions = new WatchExpressionsView(DebuggerController,
|
||||
DebuggerView);
|
@ -70,8 +70,17 @@ browser.jar:
|
||||
content/browser/devtools/debugger.css (debugger/debugger.css)
|
||||
content/browser/devtools/debugger-controller.js (debugger/debugger-controller.js)
|
||||
content/browser/devtools/debugger-view.js (debugger/debugger-view.js)
|
||||
content/browser/devtools/debugger-toolbar.js (debugger/debugger-toolbar.js)
|
||||
content/browser/devtools/debugger-panes.js (debugger/debugger-panes.js)
|
||||
content/browser/devtools/debugger/sources-view.js (debugger/views/sources-view.js)
|
||||
content/browser/devtools/debugger/variable-bubble-view.js (debugger/views/variable-bubble-view.js)
|
||||
content/browser/devtools/debugger/tracer-view.js (debugger/views/tracer-view.js)
|
||||
content/browser/devtools/debugger/watch-expressions-view.js (debugger/views/watch-expressions-view.js)
|
||||
content/browser/devtools/debugger/event-listeners-view.js (debugger/views/event-listeners-view.js)
|
||||
content/browser/devtools/debugger/global-search-view.js (debugger/views/global-search-view.js)
|
||||
content/browser/devtools/debugger/toolbar-view.js (debugger/views/toolbar-view.js)
|
||||
content/browser/devtools/debugger/options-view.js (debugger/views/options-view.js)
|
||||
content/browser/devtools/debugger/stack-frames-view.js (debugger/views/stack-frames-view.js)
|
||||
content/browser/devtools/debugger/stack-frames-classic-view.js (debugger/views/stack-frames-classic-view.js)
|
||||
content/browser/devtools/debugger/filter-view.js (debugger/views/filter-view.js)
|
||||
content/browser/devtools/debugger/utils.js (debugger/utils.js)
|
||||
content/browser/devtools/shadereditor.xul (shadereditor/shadereditor.xul)
|
||||
content/browser/devtools/shadereditor.js (shadereditor/shadereditor.js)
|
||||
|
@ -558,8 +558,10 @@ NetworkEventsHandler.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let { actor, startedDateTime, method, url, isXHR } = aPacket.eventActor;
|
||||
NetMonitorView.RequestsMenu.addRequest(actor, startedDateTime, method, url, isXHR);
|
||||
let { actor, startedDateTime, method, url, isXHR, fromCache } = aPacket.eventActor;
|
||||
NetMonitorView.RequestsMenu.addRequest(
|
||||
actor, startedDateTime, method, url, isXHR, fromCache
|
||||
);
|
||||
window.emit(EVENTS.NETWORK_EVENT, actor);
|
||||
},
|
||||
|
||||
|
@ -502,8 +502,10 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
* Specifies the request's url.
|
||||
* @param boolean aIsXHR
|
||||
* True if this request was initiated via XHR.
|
||||
* @param boolean aFromCache
|
||||
* Indicates if the result came from the browser cache
|
||||
*/
|
||||
addRequest: function(aId, aStartedDateTime, aMethod, aUrl, aIsXHR) {
|
||||
addRequest: function(aId, aStartedDateTime, aMethod, aUrl, aIsXHR, aFromCache) {
|
||||
// Convert the received date/time string to a unix timestamp.
|
||||
let unixTime = Date.parse(aStartedDateTime);
|
||||
|
||||
@ -521,7 +523,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
startedMillis: unixTime,
|
||||
method: aMethod,
|
||||
url: aUrl,
|
||||
isXHR: aIsXHR
|
||||
isXHR: aIsXHR,
|
||||
fromCache: aFromCache
|
||||
}
|
||||
});
|
||||
|
||||
@ -1237,13 +1240,20 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
break;
|
||||
case "status":
|
||||
requestItem.attachment.status = value;
|
||||
this.updateMenuView(requestItem, key, value);
|
||||
this.updateMenuView(requestItem, key, {
|
||||
status: value,
|
||||
cached: requestItem.attachment.fromCache
|
||||
});
|
||||
break;
|
||||
case "statusText":
|
||||
requestItem.attachment.statusText = value;
|
||||
this.updateMenuView(requestItem, key,
|
||||
requestItem.attachment.status + " " +
|
||||
requestItem.attachment.statusText);
|
||||
let text = (requestItem.attachment.status + " " +
|
||||
requestItem.attachment.statusText);
|
||||
if(requestItem.attachment.fromCache) {
|
||||
text += " (cached)";
|
||||
}
|
||||
|
||||
this.updateMenuView(requestItem, key, text);
|
||||
break;
|
||||
case "headersSize":
|
||||
requestItem.attachment.headersSize = value;
|
||||
@ -1253,8 +1263,14 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this.updateMenuView(requestItem, key, value);
|
||||
break;
|
||||
case "transferredSize":
|
||||
requestItem.attachment.transferredSize = value;
|
||||
this.updateMenuView(requestItem, key, value);
|
||||
if(requestItem.attachment.fromCache) {
|
||||
requestItem.attachment.transferredSize = 0;
|
||||
this.updateMenuView(requestItem, key, 'cached');
|
||||
}
|
||||
else {
|
||||
requestItem.attachment.transferredSize = value;
|
||||
this.updateMenuView(requestItem, key, value);
|
||||
}
|
||||
break;
|
||||
case "mimeType":
|
||||
requestItem.attachment.mimeType = value;
|
||||
@ -1278,7 +1294,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
break;
|
||||
case "eventTimings":
|
||||
requestItem.attachment.eventTimings = value;
|
||||
this._createWaterfallView(requestItem, value.timings);
|
||||
this._createWaterfallView(
|
||||
requestItem, value.timings, requestItem.attachment.fromCache
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1385,7 +1403,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
}
|
||||
case "remoteAddress":
|
||||
let domain = $(".requests-menu-domain", target);
|
||||
let tooltip = domain.getAttribute("value") + " (" + aValue + ")";
|
||||
let tooltip = (domain.getAttribute("value") +
|
||||
(aValue ? " (" + aValue + ")" : ""));
|
||||
domain.setAttribute("tooltiptext", tooltip);
|
||||
break;
|
||||
case "securityState": {
|
||||
@ -1399,9 +1418,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
}
|
||||
case "status": {
|
||||
let node = $(".requests-menu-status", target);
|
||||
node.setAttribute("code", aValue.cached ? "cached" : aValue.status);
|
||||
let codeNode = $(".requests-menu-status-code", target);
|
||||
codeNode.setAttribute("value", aValue);
|
||||
node.setAttribute("code", aValue);
|
||||
codeNode.setAttribute("value", aValue.status);
|
||||
break;
|
||||
}
|
||||
case "statusText": {
|
||||
@ -1419,15 +1438,22 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
break;
|
||||
}
|
||||
case "transferredSize": {
|
||||
let node = $(".requests-menu-transferred", target);
|
||||
|
||||
let text;
|
||||
if (aValue === null) {
|
||||
text = L10N.getStr("networkMenu.sizeUnavailable");
|
||||
} else {
|
||||
}
|
||||
else if(aValue === "cached") {
|
||||
text = aValue;
|
||||
node.classList.add('theme-comment');
|
||||
}
|
||||
else {
|
||||
let kb = aValue / 1024;
|
||||
let size = L10N.numberWithDecimals(kb, CONTENT_SIZE_DECIMALS);
|
||||
text = L10N.getFormatStr("networkMenu.sizeKB", size);
|
||||
}
|
||||
let node = $(".requests-menu-transferred", target);
|
||||
|
||||
node.setAttribute("value", text);
|
||||
node.setAttribute("tooltiptext", text);
|
||||
break;
|
||||
@ -1472,8 +1498,10 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
* The network request item in this container.
|
||||
* @param object aTimings
|
||||
* An object containing timing information.
|
||||
* @param boolean aFromCache
|
||||
* Indicates if the result came from the browser cache
|
||||
*/
|
||||
_createWaterfallView: function(aItem, aTimings) {
|
||||
_createWaterfallView: function(aItem, aTimings, aFromCache) {
|
||||
let { target, attachment } = aItem;
|
||||
let sections = ["dns", "connect", "send", "wait", "receive"];
|
||||
// Skipping "blocked" because it doesn't work yet.
|
||||
@ -1481,6 +1509,11 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
let timingsNode = $(".requests-menu-timings", target);
|
||||
let timingsTotal = $(".requests-menu-timings-total", timingsNode);
|
||||
|
||||
if(aFromCache) {
|
||||
timingsTotal.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a set of boxes representing timing information.
|
||||
for (let key of sections) {
|
||||
let width = aTimings[key];
|
||||
@ -2356,7 +2389,7 @@ NetworkDetailsView.prototype = {
|
||||
}
|
||||
|
||||
if (aData.status) {
|
||||
$("#headers-summary-status-circle").setAttribute("code", aData.status);
|
||||
$("#headers-summary-status-circle").setAttribute("code", aData.fromCache ? "cached" : aData.status);
|
||||
$("#headers-summary-status-value").setAttribute("value", aData.status + " " + aData.statusText);
|
||||
$("#headers-summary-status").removeAttribute("hidden");
|
||||
} else {
|
||||
@ -2727,7 +2760,9 @@ NetworkDetailsView.prototype = {
|
||||
|
||||
let tabboxWidth = $("#details-pane").getAttribute("width");
|
||||
let availableWidth = tabboxWidth / 2; // Other nodes also take some space.
|
||||
let scale = Math.max(availableWidth / aResponse.totalTime, 0);
|
||||
let scale = (aResponse.totalTime > 0 ?
|
||||
Math.max(availableWidth / aResponse.totalTime, 0) :
|
||||
0);
|
||||
|
||||
$("#timings-summary-blocked .requests-menu-timings-box")
|
||||
.setAttribute("width", blocked * scale);
|
||||
|
@ -38,6 +38,7 @@ support-files =
|
||||
[browser_net_accessibility-01.js]
|
||||
[browser_net_accessibility-02.js]
|
||||
[browser_net_autoscroll.js]
|
||||
[browser_net_cached-status.js]
|
||||
[browser_net_charts-01.js]
|
||||
[browser_net_charts-02.js]
|
||||
[browser_net_charts-03.js]
|
||||
|
105
browser/devtools/netmonitor/test/browser_net_cached-status.js
Normal file
105
browser/devtools/netmonitor/test/browser_net_cached-status.js
Normal file
@ -0,0 +1,105 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if cached requests have the correct status code
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [tab, debuggee, monitor] = yield initNetMonitor(STATUS_CODES_URL, null, true);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, L10N, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu, NetworkDetails } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
NetworkDetails._params.lazyEmpty = false;
|
||||
|
||||
const REQUEST_DATA = [
|
||||
{
|
||||
method: 'GET',
|
||||
uri: STATUS_CODES_SJS + "?sts=ok&cached",
|
||||
details: {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
type: "plain",
|
||||
fullMimeType: "text/plain; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
uri: STATUS_CODES_SJS + "?sts=redirect&cached",
|
||||
details: {
|
||||
status: 301,
|
||||
statusText: 'Moved Permanently',
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
uri: 'http://example.com/redirected',
|
||||
details: {
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
uri: STATUS_CODES_SJS + "?sts=ok&cached",
|
||||
details: {
|
||||
status: 200,
|
||||
statusText: "OK (cached)",
|
||||
fromCache: true,
|
||||
type: "plain",
|
||||
fullMimeType: "text/plain; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
uri: STATUS_CODES_SJS + "?sts=redirect&cached",
|
||||
details: {
|
||||
status: 301,
|
||||
statusText: "Moved Permanently (cached)",
|
||||
fromCache: true,
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
uri: 'http://example.com/redirected',
|
||||
details: {
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
info("Performing requests #1...");
|
||||
debuggee.performCachedRequests();
|
||||
yield waitForNetworkEvents(monitor, 3);
|
||||
|
||||
info("Performing requests #2...");
|
||||
debuggee.performCachedRequests();
|
||||
yield waitForNetworkEvents(monitor, 3);
|
||||
|
||||
let index = 0;
|
||||
for (let request of REQUEST_DATA) {
|
||||
let item = RequestsMenu.getItemAtIndex(index);
|
||||
|
||||
info("Verifying request #" + index);
|
||||
yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
yield teardown(monitor);
|
||||
finish();
|
||||
});
|
@ -132,7 +132,7 @@ function toggleCache(aTarget, aDisabled) {
|
||||
return reconfigureTab(aTarget, options).then(() => navigationFinished);
|
||||
}
|
||||
|
||||
function initNetMonitor(aUrl, aWindow) {
|
||||
function initNetMonitor(aUrl, aWindow, aEnableCache) {
|
||||
info("Initializing a network monitor pane.");
|
||||
|
||||
return Task.spawn(function*() {
|
||||
@ -144,8 +144,10 @@ function initNetMonitor(aUrl, aWindow) {
|
||||
yield target.makeRemote();
|
||||
info("Target remoted.");
|
||||
|
||||
yield toggleCache(target, true);
|
||||
info("Cache disabled when the current and all future toolboxes are open.");
|
||||
if(!aEnableCache) {
|
||||
yield toggleCache(target, true);
|
||||
info("Cache disabled when the current and all future toolboxes are open.");
|
||||
}
|
||||
|
||||
let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
|
||||
info("Netork monitor pane shown successfully.");
|
||||
@ -268,7 +270,8 @@ function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
|
||||
info("Widget index of item: " + widgetIndex);
|
||||
info("Visible index of item: " + visibleIndex);
|
||||
|
||||
let { fuzzyUrl, status, statusText, type, fullMimeType, transferred, size, time } = aData;
|
||||
let { fuzzyUrl, status, statusText, type, fullMimeType,
|
||||
transferred, size, time, fromCache } = aData;
|
||||
let { attachment, target } = aRequestItem
|
||||
|
||||
let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
|
||||
@ -314,7 +317,7 @@ function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
|
||||
info("Displayed status: " + value);
|
||||
info("Displayed code: " + codeValue);
|
||||
info("Tooltip status: " + tooltip);
|
||||
is(value, status, "The displayed status is correct.");
|
||||
is(value, fromCache ? "cached" : status, "The displayed status is correct.");
|
||||
is(codeValue, status, "The displayed status code is correct.");
|
||||
is(tooltip, status + " " + statusText, "The tooltip status is correct.");
|
||||
}
|
||||
|
@ -40,6 +40,15 @@
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function performCachedRequests() {
|
||||
get("sjs_status-codes-test-server.sjs?sts=ok&cached", function() {
|
||||
get("sjs_status-codes-test-server.sjs?sts=redirect&cached", function() {
|
||||
// Done.
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
@ -7,7 +7,8 @@ function handleRequest(request, response) {
|
||||
response.processAsync();
|
||||
|
||||
let params = request.queryString.split("&");
|
||||
let status = params.filter((s) => s.contains("sts="))[0].split("=")[1];
|
||||
let status = params.filter(s => s.contains("sts="))[0].split("=")[1];
|
||||
let cached = params.filter(s => s === 'cached').length !== 0;
|
||||
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.initWithCallback(() => {
|
||||
@ -29,11 +30,24 @@ function handleRequest(request, response) {
|
||||
case "500":
|
||||
response.setStatusLine(request.httpVersion, 501, "Not Implemented");
|
||||
break;
|
||||
case "ok":
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
break;
|
||||
case "redirect":
|
||||
response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
|
||||
response.setHeader("Location", "http://example.com/redirected");
|
||||
break;
|
||||
}
|
||||
|
||||
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
if(!cached) {
|
||||
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
}
|
||||
else {
|
||||
response.setHeader("Cache-Control", "no-transform,public,max-age=300,s-maxage=900");
|
||||
response.setHeader("Expires", "Thu, 01 Dec 2100 20:00:00 GMT");
|
||||
}
|
||||
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
|
||||
response.write("Hello status code " + status + "!");
|
||||
|
@ -184,27 +184,33 @@
|
||||
<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>
|
||||
@ -231,12 +237,12 @@
|
||||
type="allocations"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalAlloc1;"
|
||||
tooltiptext="&profilerUI.table.totalAllocTooltip;"/>
|
||||
tooltiptext="&profilerUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-allocations"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfAlloc1;"
|
||||
tooltiptext="&profilerUI.table.selfAllocTooltip;"/>
|
||||
tooltiptext="&profilerUI.table.selfAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="function"
|
||||
crop="end"
|
||||
|
@ -87,7 +87,9 @@ Cu.import("resource://gre/modules/devtools/event-emitter.js");
|
||||
function editableField(aOptions)
|
||||
{
|
||||
return editableItem(aOptions, function(aElement, aEvent) {
|
||||
new InplaceEditor(aOptions, aEvent);
|
||||
if (!aOptions.element.inplaceEditor) {
|
||||
new InplaceEditor(aOptions, aEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -282,7 +284,7 @@ InplaceEditor.prototype = {
|
||||
this.elt.style.display = this.originalDisplay;
|
||||
this.elt.focus();
|
||||
|
||||
this.elt.parentNode.removeChild(this.input);
|
||||
this.input.remove();
|
||||
this.input = null;
|
||||
|
||||
delete this.elt.inplaceEditor;
|
||||
@ -327,7 +329,7 @@ InplaceEditor.prototype = {
|
||||
if (!this._measurement) {
|
||||
return;
|
||||
}
|
||||
this._measurement.parentNode.removeChild(this._measurement);
|
||||
this._measurement.remove();
|
||||
delete this._measurement;
|
||||
},
|
||||
|
||||
|
@ -13,6 +13,7 @@ add_task(function*() {
|
||||
yield promiseTab("data:text/html;charset=utf-8,inline editor tests");
|
||||
let [host, win, doc] = yield createHost();
|
||||
|
||||
yield testMultipleInitialization(doc);
|
||||
yield testReturnCommit(doc);
|
||||
yield testBlurCommit(doc);
|
||||
yield testAdvanceCharCommit(doc);
|
||||
@ -21,6 +22,24 @@ add_task(function*() {
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function testMultipleInitialization(doc) {
|
||||
doc.body.innerHTML = "";
|
||||
let options = {};
|
||||
let span = options.element = createSpan(doc);
|
||||
|
||||
info("Creating multiple inplace-editor fields");
|
||||
editableField(options);
|
||||
editableField(options);
|
||||
|
||||
info("Clicking on the inplace-editor field to turn to edit mode");
|
||||
span.click();
|
||||
|
||||
is (span.style.display, "none", "The original <span> is hidden");
|
||||
is (doc.querySelectorAll("input").length, 1, "Only one <input>");
|
||||
is (doc.querySelectorAll("span").length, 2, "Correct number of <span> elements");
|
||||
is (doc.querySelectorAll("span.autosizer").length, 1, "There is an autosizer element");
|
||||
}
|
||||
|
||||
function testReturnCommit(doc) {
|
||||
info("Testing that pressing return commits the new value");
|
||||
let def = promise.defer();
|
||||
@ -98,7 +117,7 @@ function onDone(value, isCommit, def) {
|
||||
}
|
||||
|
||||
function createInplaceEditorAndClick(options, doc) {
|
||||
clearBody(doc);
|
||||
doc.body.innerHTML = "";
|
||||
let span = options.element = createSpan(doc);
|
||||
|
||||
info("Creating an inplace-editor field");
|
||||
@ -108,11 +127,6 @@ function createInplaceEditorAndClick(options, doc) {
|
||||
span.click();
|
||||
}
|
||||
|
||||
function clearBody(doc) {
|
||||
info("Clearing the page body");
|
||||
doc.body.innerHTML = "";
|
||||
}
|
||||
|
||||
function createSpan(doc) {
|
||||
info("Creating a new span element");
|
||||
let span = doc.createElement("span");
|
||||
|
@ -102,7 +102,8 @@ const CM_MAPPING = [
|
||||
"clearHistory",
|
||||
"openDialog",
|
||||
"refresh",
|
||||
"getScrollInfo"
|
||||
"getScrollInfo",
|
||||
"getViewport"
|
||||
];
|
||||
|
||||
const { cssProperties, cssValues, cssColors } = getCSSKeywords();
|
||||
|
@ -91,8 +91,7 @@ function StyleSheetEditor(styleSheet, win, file, isNew, walker, highlighter) {
|
||||
selection: {
|
||||
start: {line: 0, ch: 0},
|
||||
end: {line: 0, ch: 0}
|
||||
},
|
||||
topIndex: 0 // the first visible line
|
||||
}
|
||||
};
|
||||
|
||||
this._styleSheetFilePath = null;
|
||||
@ -400,7 +399,6 @@ StyleSheetEditor.prototype = {
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
sourceEditor.setFirstVisibleLine(this._state.topIndex);
|
||||
sourceEditor.setSelection(this._state.selection.start,
|
||||
this._state.selection.end);
|
||||
|
||||
@ -446,7 +444,9 @@ StyleSheetEditor.prototype = {
|
||||
*/
|
||||
onShow: function() {
|
||||
if (this.sourceEditor) {
|
||||
this.sourceEditor.setFirstVisibleLine(this._state.topIndex);
|
||||
// CodeMirror needs refresh to restore scroll position after hiding and
|
||||
// showing the editor.
|
||||
this.sourceEditor.refresh();
|
||||
}
|
||||
this.focus();
|
||||
},
|
||||
|
@ -44,6 +44,7 @@ support-files =
|
||||
sourcemaps-watching.html
|
||||
test_private.css
|
||||
test_private.html
|
||||
doc_long.css
|
||||
doc_uncached.css
|
||||
doc_uncached.html
|
||||
doc_xulpage.xul
|
||||
@ -75,6 +76,7 @@ skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
|
||||
[browser_styleeditor_pretty.js]
|
||||
[browser_styleeditor_private_perwindowpb.js]
|
||||
[browser_styleeditor_reload.js]
|
||||
[browser_styleeditor_scroll.js]
|
||||
[browser_styleeditor_sv_keynav.js]
|
||||
[browser_styleeditor_sv_resize.js]
|
||||
[browser_styleeditor_selectstylesheet.js]
|
||||
|
@ -26,12 +26,15 @@ add_task(function() {
|
||||
|
||||
info("Checking Netmonitor contents.");
|
||||
let requestsForCss = 0;
|
||||
let attachments = [];
|
||||
for (let item of netmonitor._view.RequestsMenu) {
|
||||
if (item.attachment.url.endsWith("doc_uncached.css")) {
|
||||
requestsForCss++;
|
||||
attachments.push(item.attachment);
|
||||
}
|
||||
}
|
||||
|
||||
is(requestsForCss, 1,
|
||||
"Got one request for doc_uncached.css after Style Editor was loaded.");
|
||||
is(attachments.length, 2,
|
||||
"Got two requests for doc_uncached.css after Style Editor was loaded.");
|
||||
ok(attachments[1].fromCache,
|
||||
"Second request was loaded from browser cache");
|
||||
});
|
||||
|
@ -0,0 +1,91 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that editor scrolls to correct line if it's selected with
|
||||
// * selectStyleSheet (specified line)
|
||||
// * click on the sidebar item (line before the editor was unselected)
|
||||
// See bug 1148086.
|
||||
|
||||
const SIMPLE = TEST_BASE_HTTP + "simple.css";
|
||||
const LONG = TEST_BASE_HTTP + "doc_long.css";
|
||||
const DOCUMENT_WITH_LONG_SHEET = "data:text/html;charset=UTF-8," +
|
||||
encodeURIComponent(
|
||||
["<!DOCTYPE html>",
|
||||
"<html>",
|
||||
" <head>",
|
||||
" <title>Editor scroll test page</title>",
|
||||
' <link rel="stylesheet" type="text/css" href="'+SIMPLE+'">',
|
||||
' <link rel="stylesheet" type="text/css" href="'+LONG+'">',
|
||||
" </head>",
|
||||
" <body>Editor scroll test page</body>",
|
||||
"</html>"
|
||||
].join("\n"));
|
||||
const LINE_TO_SELECT = 201;
|
||||
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(DOCUMENT_WITH_LONG_SHEET);
|
||||
|
||||
is(ui.editors.length, 2, "Two editors present.");
|
||||
|
||||
let simpleEditor = ui.editors[0];
|
||||
let longEditor = ui.editors[1];
|
||||
|
||||
info(`Selecting doc_long.css and scrolling to line ${LINE_TO_SELECT}`);
|
||||
|
||||
// We need to wait for editor-selected if we want to check the scroll
|
||||
// position as scrolling occurs after selectStyleSheet resolves but before the
|
||||
// event is emitted.
|
||||
let selectEventPromise = waitForEditorToBeSelected(longEditor, ui);
|
||||
ui.selectStyleSheet(longEditor.styleSheet, LINE_TO_SELECT);
|
||||
yield selectEventPromise;
|
||||
|
||||
info("Checking that the correct line is visible after initial load");
|
||||
|
||||
let { from, to } = longEditor.sourceEditor.getViewport();
|
||||
info(`Lines ${from}-${to} are visible (expected ${LINE_TO_SELECT}).`);
|
||||
|
||||
ok(from <= LINE_TO_SELECT, "The editor scrolled too much.");
|
||||
ok(to >= LINE_TO_SELECT, "The editor scrolled too little.");
|
||||
|
||||
let initialScrollTop = longEditor.sourceEditor.getScrollInfo().top;
|
||||
info(`Storing scrollTop = ${initialScrollTop} for later comparison.`);
|
||||
|
||||
info("Selecting the first editor (simple.css)");
|
||||
yield ui.selectStyleSheet(simpleEditor.styleSheet);
|
||||
|
||||
info("Selecting doc_long.css again.");
|
||||
selectEventPromise = waitForEditorToBeSelected(longEditor, ui);
|
||||
|
||||
// Can't use ui.selectStyleSheet here as it will scroll the editor back to top
|
||||
// and we want to check that the previous scroll position is restored.
|
||||
let summary = yield ui.getEditorSummary(longEditor);
|
||||
ui._view.activeSummary = summary;
|
||||
|
||||
info("Waiting for doc_long.css to be selected.");
|
||||
yield selectEventPromise;
|
||||
|
||||
let scrollTop = longEditor.sourceEditor.getScrollInfo().top;
|
||||
is(scrollTop, initialScrollTop,
|
||||
"Scroll top was restored after the sheet was selected again.");
|
||||
});
|
||||
|
||||
/**
|
||||
* A helper that waits "editor-selected" event for given editor.
|
||||
*
|
||||
* @param {StyleSheetEditor} editor
|
||||
* The editor to wait for.
|
||||
* @param {StyleEditorUI} ui
|
||||
* The StyleEditorUI the editor belongs to.
|
||||
*/
|
||||
let waitForEditorToBeSelected = Task.async(function* (editor, ui) {
|
||||
info(`Waiting for ${editor.friendlyName} to be selected.`);
|
||||
let selected = yield ui.once("editor-selected");
|
||||
while (selected != editor) {
|
||||
info(`Ignored editor-selected for editor ${editor.friendlyName}.`);
|
||||
selected = yield ui.once("editor-selected");
|
||||
}
|
||||
|
||||
info(`Got editor-selected for ${editor.friendlyName}.`);
|
||||
});
|
403
browser/devtools/styleeditor/test/doc_long.css
Normal file
403
browser/devtools/styleeditor/test/doc_long.css
Normal file
@ -0,0 +1,403 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
div {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 7;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 13;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 14;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 16;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 17;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 18;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 19;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 21;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 22;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 23;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 24;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 25;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 26;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 27;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 28;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 29;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 31;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 32;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 33;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 34;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 35;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 36;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 37;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 38;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 39;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 41;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 42;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 43;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 44;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 45;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 46;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 47;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 48;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 49;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 51;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 52;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 53;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 54;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 55;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 56;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 57;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 58;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 59;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 61;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 62;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 63;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 64;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 65;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 66;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 67;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 68;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 69;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 70;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 71;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 72;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 73;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 74;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 75;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 76;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 77;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 78;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 79;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 81;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 82;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 83;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 84;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 85;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 86;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 87;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 88;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 89;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 91;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 92;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 93;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 94;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 95;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 96;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 97;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 98;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
div {
|
||||
z-index: 100;
|
||||
}
|
@ -80,6 +80,7 @@ skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work wit
|
||||
[browser_ruleview_edit-property-order.js]
|
||||
[browser_ruleview_edit-property_01.js]
|
||||
[browser_ruleview_edit-property_02.js]
|
||||
[browser_ruleview_edit-property_03.js]
|
||||
[browser_ruleview_edit-selector-commit.js]
|
||||
[browser_ruleview_edit-selector_01.js]
|
||||
[browser_ruleview_edit-selector_02.js]
|
||||
|
@ -4,28 +4,34 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
// Testing various inplace-editor behaviors in the rule-view
|
||||
// FIXME: To be split in several test files, and some of the inplace-editor
|
||||
// focus/blur/commit/revert stuff should be factored out in head.js
|
||||
// Testing adding new properties via the inplace-editors in the rule
|
||||
// view.
|
||||
// FIXME: some of the inplace-editor focus/blur/commit/revert stuff
|
||||
// should be factored out in head.js
|
||||
|
||||
let TEST_URL = 'url("' + TEST_URL_ROOT + 'doc_test_image.png")';
|
||||
let PAGE_CONTENT = [
|
||||
let BACKGROUND_IMAGE_URL = 'url("' + TEST_URL_ROOT + 'doc_test_image.png")';
|
||||
let TEST_URI = [
|
||||
'<style type="text/css">',
|
||||
' #testid {',
|
||||
' background-color: blue;',
|
||||
' }',
|
||||
' .testclass {',
|
||||
' background-color: green;',
|
||||
' }',
|
||||
'#testid {',
|
||||
' color: red;',
|
||||
' background-color: blue;',
|
||||
'}',
|
||||
'.testclass, .unmatched {',
|
||||
' background-color: green;',
|
||||
'}',
|
||||
'</style>',
|
||||
'<div id="testid" class="testclass">Styled Node</div>'
|
||||
'<div id="testid" class="testclass">Styled Node</div>',
|
||||
'<div id="testid2">Styled Node</div>'
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
let tab = yield addTab("data:text/html;charset=utf-8,test rule view user changes");
|
||||
let TEST_DATA = [
|
||||
{ name: "border-color", value: "red", isValid: true },
|
||||
{ name: "background-image", value: BACKGROUND_IMAGE_URL, isValid: true },
|
||||
{ name: "border", value: "solid 1px foo", isValid: false },
|
||||
];
|
||||
|
||||
info("Creating the test document");
|
||||
content.document.body.innerHTML = PAGE_CONTENT;
|
||||
add_task(function*() {
|
||||
let tab = yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
|
||||
info("Opening the rule-view");
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
@ -33,15 +39,17 @@ add_task(function*() {
|
||||
info("Selecting the test element");
|
||||
yield selectNode("#testid", inspector);
|
||||
|
||||
yield testEditProperty(view, "border-color", "red", tab.linkedBrowser);
|
||||
yield testEditProperty(view, "background-image", TEST_URL, tab.linkedBrowser);
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
for (let {name, value, isValid} of TEST_DATA) {
|
||||
yield testEditProperty(ruleEditor, name, value, isValid);
|
||||
}
|
||||
});
|
||||
|
||||
function* testEditProperty(view, name, value, browser) {
|
||||
function* testEditProperty(ruleEditor, name, value, isValid) {
|
||||
info("Test editing existing property name/value fields");
|
||||
|
||||
let idRuleEditor = getRuleViewRuleEditor(view, 1);
|
||||
let propEditor = idRuleEditor.rule.textProps[0].editor;
|
||||
let doc = ruleEditor.doc;
|
||||
let propEditor = ruleEditor.rule.textProps[0].editor;
|
||||
|
||||
info("Focusing an existing property name in the rule-view");
|
||||
let editor = yield focusEditableField(propEditor.nameSpan, 32, 1);
|
||||
@ -50,29 +58,29 @@ function* testEditProperty(view, name, value, browser) {
|
||||
let input = editor.input;
|
||||
|
||||
info("Entering a new property name, including : to commit and focus the value");
|
||||
let onValueFocus = once(idRuleEditor.element, "focus", true);
|
||||
let onModifications = idRuleEditor.rule._applyingModifications;
|
||||
let onValueFocus = once(ruleEditor.element, "focus", true);
|
||||
let onModifications = ruleEditor.rule._applyingModifications;
|
||||
for (let ch of name + ":") {
|
||||
EventUtils.sendChar(ch, view.doc.defaultView);
|
||||
EventUtils.sendChar(ch, doc.defaultView);
|
||||
}
|
||||
yield onValueFocus;
|
||||
yield onModifications;
|
||||
|
||||
// Getting the value editor after focus
|
||||
editor = inplaceEditor(view.doc.activeElement);
|
||||
editor = inplaceEditor(doc.activeElement);
|
||||
input = editor.input;
|
||||
is(inplaceEditor(propEditor.valueSpan), editor, "Focus moved to the value.");
|
||||
|
||||
info("Entering a new value, including ; to commit and blur the value");
|
||||
let onBlur = once(input, "blur");
|
||||
onModifications = idRuleEditor.rule._applyingModifications;
|
||||
onModifications = ruleEditor.rule._applyingModifications;
|
||||
for (let ch of value + ";") {
|
||||
EventUtils.sendChar(ch, view.doc.defaultView);
|
||||
EventUtils.sendChar(ch, doc.defaultView);
|
||||
}
|
||||
yield onBlur;
|
||||
yield onModifications;
|
||||
|
||||
is(propEditor.isValid(), true, value + " is a valid entry");
|
||||
is(propEditor.isValid(), isValid, value + " is " + isValid ? "valid" : "invalid");
|
||||
|
||||
info("Checking that the style property was changed on the content page");
|
||||
let propValue = yield executeInContent("Test:GetRulePropertyValue", {
|
||||
@ -80,5 +88,10 @@ function* testEditProperty(view, name, value, browser) {
|
||||
ruleIndex: 0,
|
||||
name
|
||||
});
|
||||
is(propValue, value, name + " should have been set.");
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
is(propValue, value, name + " should have been set.");
|
||||
} else {
|
||||
isnot(propValue, value, name + " shouldn't have been set.");
|
||||
}
|
||||
}
|
@ -6,28 +6,28 @@
|
||||
|
||||
// Test several types of rule-view property edition
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8,browser_ruleview_ui.js");
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
let TEST_URI = [
|
||||
'<style type="text/css">',
|
||||
'#testid {',
|
||||
' background-color: blue;',
|
||||
'}',
|
||||
'.testclass, .unmatched {',
|
||||
' background-color: green;',
|
||||
'}',
|
||||
'</style>',
|
||||
'<div id="testid" class="testclass">Styled Node</div>',
|
||||
'<div id="testid2">Styled Node</div>'
|
||||
].join("\n");
|
||||
|
||||
info("Creating the test document");
|
||||
let style = "" +
|
||||
"#testid {" +
|
||||
" background-color: blue;" +
|
||||
"}" +
|
||||
".testclass, .unmatched {" +
|
||||
" background-color: green;" +
|
||||
"}";
|
||||
let styleNode = addStyle(content.document, style);
|
||||
content.document.body.innerHTML = "<div id='testid' class='testclass'>Styled Node</div>" +
|
||||
"<div id='testid2'>Styled Node</div>";
|
||||
add_task(function*() {
|
||||
let tab = yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testEditProperty(inspector, view);
|
||||
yield testDisableProperty(inspector, view);
|
||||
yield testPropertyStillMarkedDirty(inspector, view);
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function* testEditProperty(inspector, ruleView) {
|
||||
|
@ -0,0 +1,60 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that emptying out an existing value removes the property and
|
||||
// doesn't cause any other issues. See also Bug 1150780.
|
||||
|
||||
let TEST_URI = [
|
||||
'<style type="text/css">',
|
||||
'#testid {',
|
||||
' color: red;',
|
||||
' background-color: blue;',
|
||||
' font-size: 12px;',
|
||||
'}',
|
||||
'.testclass, .unmatched {',
|
||||
' background-color: green;',
|
||||
'}',
|
||||
'</style>',
|
||||
'<div id="testid" class="testclass">Styled Node</div>',
|
||||
'<div id="testid2">Styled Node</div>'
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
let tab = yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
|
||||
info("Opening the rule-view");
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
|
||||
info("Selecting the test element");
|
||||
yield selectNode("#testid", inspector);
|
||||
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
let doc = ruleEditor.doc;
|
||||
|
||||
let propEditor = ruleEditor.rule.textProps[1].editor;
|
||||
let editor = yield focusEditableField(propEditor.valueSpan);
|
||||
|
||||
info("Deleting all the text out of a value field");
|
||||
yield sendCharsAndWaitForFocus(ruleEditor.element, ["VK_DELETE", "VK_RETURN"]);
|
||||
yield ruleEditor.rule._applyingModifications;
|
||||
|
||||
info("Pressing enter a couple times to cycle through editors");
|
||||
yield sendCharsAndWaitForFocus(ruleEditor.element, ["VK_RETURN"]);
|
||||
yield sendCharsAndWaitForFocus(ruleEditor.element, ["VK_RETURN"]);
|
||||
|
||||
yield ruleEditor.rule._applyingModifications;
|
||||
|
||||
isnot (ruleEditor.rule.textProps[1].editor.nameSpan.style.display, "none", "The name span is visible");
|
||||
is (ruleEditor.rule.textProps.length, 2, "Correct number of props");
|
||||
});
|
||||
|
||||
function* sendCharsAndWaitForFocus(element, chars) {
|
||||
let onFocus = once(element, "focus", true);
|
||||
for (let ch of chars) {
|
||||
EventUtils.sendChar(ch, element.ownerDocument.defaultView);
|
||||
}
|
||||
yield onFocus;
|
||||
}
|
@ -31,5 +31,8 @@ webide.jar:
|
||||
content/project-listing.xhtml (project-listing.xhtml)
|
||||
content/project-listing.js (project-listing.js)
|
||||
content/project-panel.js (project-panel.js)
|
||||
content/runtime-panel.js (runtime-panel.js)
|
||||
content/runtime-listing.xhtml (runtime-listing.xhtml)
|
||||
content/runtime-listing.js (runtime-listing.js)
|
||||
content/simulator.js (simulator.js)
|
||||
content/simulator.xhtml (simulator.xhtml)
|
||||
|
@ -4,38 +4,24 @@
|
||||
|
||||
const Cu = Components.utils;
|
||||
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
|
||||
const {AppManager} = require("devtools/webide/app-manager");
|
||||
const ProjectList = require("devtools/webide/project-list");
|
||||
|
||||
let projectList = new ProjectList(window, window.parent);
|
||||
|
||||
window.addEventListener("load", function onLoad() {
|
||||
window.removeEventListener("load", onLoad);
|
||||
AppManager.on("app-manager-update", onAppManagerUpdate);
|
||||
window.removeEventListener("load", onLoad, true);
|
||||
document.getElementById("new-app").onclick = CreateNewApp;
|
||||
document.getElementById("hosted-app").onclick = ImportHostedApp;
|
||||
document.getElementById("packaged-app").onclick = ImportPackagedApp;
|
||||
projectList.update();
|
||||
projectList.updateCommands();
|
||||
}, true);
|
||||
|
||||
window.addEventListener("unload", function onUnload() {
|
||||
window.removeEventListener("unload", onUnload);
|
||||
projectList = null;
|
||||
AppManager.off("app-manager-update", onAppManagerUpdate);
|
||||
projectList.destroy();
|
||||
});
|
||||
|
||||
function onAppManagerUpdate(event, what, details) {
|
||||
switch (what) {
|
||||
case "runtime-global-actors":
|
||||
case "runtime-targets":
|
||||
case "project-validated":
|
||||
case "project-removed":
|
||||
case "project":
|
||||
projectList.update(details);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function CreateNewApp() {
|
||||
projectList.newApp();
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf8"/>
|
||||
<link rel="stylesheet" href="chrome://webide/skin/project-listing.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://webide/skin/panel-listing.css" type="text/css"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://webide/content/project-listing.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -4,35 +4,31 @@
|
||||
|
||||
let ProjectPanel = {
|
||||
// TODO: Expand function to save toggle state.
|
||||
toggle: function(sidebarsEnabled, triggerPopup) {
|
||||
toggleSidebar: function() {
|
||||
document.querySelector("#project-listing-panel").setAttribute("sidebar-displayed", true);
|
||||
document.querySelector("#project-listing-splitter").setAttribute("sidebar-displayed", true);
|
||||
},
|
||||
|
||||
showPopup: function() {
|
||||
let deferred = promise.defer();
|
||||
let doc = document;
|
||||
|
||||
if (sidebarsEnabled) {
|
||||
doc.querySelector("#project-listing-panel").setAttribute("sidebar-displayed", true);
|
||||
doc.querySelector("#project-listing-splitter").setAttribute("sidebar-displayed", true);
|
||||
deferred.resolve();
|
||||
} else if (triggerPopup) {
|
||||
let panelNode = doc.querySelector("#project-panel");
|
||||
let panelVboxNode = doc.querySelector("#project-panel > #project-panel-box");
|
||||
let anchorNode = doc.querySelector("#project-panel-button > .panel-button-anchor");
|
||||
let panelNode = document.querySelector("#project-panel");
|
||||
let panelVboxNode = document.querySelector("#project-panel > #project-panel-box");
|
||||
let anchorNode = document.querySelector("#project-panel-button > .panel-button-anchor");
|
||||
|
||||
window.setTimeout(() => {
|
||||
// Open the popup only when the projects are added.
|
||||
// Not doing it in the next tick can cause mis-calculations
|
||||
// of the size of the panel.
|
||||
function onPopupShown() {
|
||||
panelNode.removeEventListener("popupshown", onPopupShown);
|
||||
deferred.resolve();
|
||||
}
|
||||
window.setTimeout(() => {
|
||||
// Open the popup only when the projects are added.
|
||||
// Not doing it in the next tick can cause mis-calculations
|
||||
// of the size of the panel.
|
||||
function onPopupShown() {
|
||||
panelNode.removeEventListener("popupshown", onPopupShown);
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
panelNode.addEventListener("popupshown", onPopupShown);
|
||||
panelNode.openPopup(anchorNode);
|
||||
panelVboxNode.scrollTop = 0;
|
||||
}, 0);
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
panelNode.addEventListener("popupshown", onPopupShown);
|
||||
panelNode.openPopup(anchorNode);
|
||||
panelVboxNode.scrollTop = 0;
|
||||
}, 0);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
61
browser/devtools/webide/content/runtime-listing.js
Normal file
61
browser/devtools/webide/content/runtime-listing.js
Normal file
@ -0,0 +1,61 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const Cu = Components.utils;
|
||||
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
|
||||
const RuntimeList = require("devtools/webide/runtime-list");
|
||||
|
||||
let runtimeList = new RuntimeList(window, window.parent);
|
||||
|
||||
window.addEventListener("load", function onLoad() {
|
||||
window.removeEventListener("load", onLoad, true);
|
||||
document.getElementById("runtime-screenshot").onclick = TakeScreenshot;
|
||||
document.getElementById("runtime-permissions").onclick = ShowPermissionsTable;
|
||||
document.getElementById("runtime-details").onclick = ShowRuntimeDetails;
|
||||
document.getElementById("runtime-disconnect").onclick = DisconnectRuntime;
|
||||
document.getElementById("runtime-preferences").onclick = ShowDevicePreferences;
|
||||
document.getElementById("runtime-settings").onclick = ShowSettings;
|
||||
document.getElementById("runtime-panel-installsimulator").onclick = ShowAddons;
|
||||
document.getElementById("runtime-panel-noadbhelper").onclick = ShowAddons;
|
||||
document.getElementById("runtime-panel-nousbdevice").onclick = ShowTroubleShooting;
|
||||
runtimeList.update();
|
||||
runtimeList.updateCommands();
|
||||
}, true);
|
||||
|
||||
window.addEventListener("unload", function onUnload() {
|
||||
window.removeEventListener("unload", onUnload);
|
||||
runtimeList.destroy();
|
||||
});
|
||||
|
||||
function TakeScreenshot() {
|
||||
runtimeList.takeScreenshot();
|
||||
}
|
||||
|
||||
function ShowRuntimeDetails() {
|
||||
runtimeList.showRuntimeDetails();
|
||||
}
|
||||
|
||||
function ShowPermissionsTable() {
|
||||
runtimeList.showPermissionsTable();
|
||||
}
|
||||
|
||||
function ShowDevicePreferences() {
|
||||
runtimeList.showDevicePreferences();
|
||||
}
|
||||
|
||||
function ShowSettings() {
|
||||
runtimeList.showSettings();
|
||||
}
|
||||
|
||||
function DisconnectRuntime() {
|
||||
window.parent.Cmds.disconnectRuntime();
|
||||
}
|
||||
|
||||
function ShowAddons() {
|
||||
runtimeList.showAddons();
|
||||
}
|
||||
|
||||
function ShowTroubleShooting() {
|
||||
runtimeList.showTroubleShooting();
|
||||
}
|
43
browser/devtools/webide/content/runtime-listing.xhtml
Normal file
43
browser/devtools/webide/content/runtime-listing.xhtml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
|
||||
%webideDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf8"/>
|
||||
<link rel="stylesheet" href="chrome://webide/skin/panel-listing.css" type="text/css"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://webide/content/runtime-listing.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="runtime-panel">
|
||||
<div id="runtime-panel-box">
|
||||
<label class="panel-header">&runtimePanel_usb;</label>
|
||||
<button class="panel-item" id="runtime-panel-nousbdevice">&runtimePanel_nousbdevice;</button>
|
||||
<button class="panel-item" id="runtime-panel-noadbhelper">&runtimePanel_noadbhelper;</button>
|
||||
<div id="runtime-panel-usb"></div>
|
||||
<label class="panel-header" id="runtime-header-wifi">&runtimePanel_wifi;</label>
|
||||
<div id="runtime-panel-wifi"></div>
|
||||
<label class="panel-header">&runtimePanel_simulator;</label>
|
||||
<div id="runtime-panel-simulator"></div>
|
||||
<button class="panel-item" id="runtime-panel-installsimulator">&runtimePanel_installsimulator;</button>
|
||||
<label class="panel-header">&runtimePanel_other;</label>
|
||||
<div id="runtime-panel-other"></div>
|
||||
<div id="runtime-actions">
|
||||
<button class="panel-item" id="runtime-details">&runtimeMenu_showDetails_label;</button>
|
||||
<button class="panel-item" id="runtime-permissions">&runtimeMenu_showPermissionTable_label;</button>
|
||||
<button class="panel-item" id="runtime-preferences">&runtimeMenu_showDevicePrefs_label;</button>
|
||||
<button class="panel-item" id="runtime-settings">&runtimeMenu_showSettings_label;</button>
|
||||
<button class="panel-item" id="runtime-screenshot">&runtimeMenu_takeScreenshot_label;</button>
|
||||
<button class="panel-item" id="runtime-disconnect">&runtimeMenu_disconnect_label;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
26
browser/devtools/webide/content/runtime-panel.js
Normal file
26
browser/devtools/webide/content/runtime-panel.js
Normal file
@ -0,0 +1,26 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let RuntimePanel = {
|
||||
// TODO: Expand function to save toggle state.
|
||||
toggleSidebar: function() {
|
||||
document.querySelector("#runtime-listing-panel").setAttribute("sidebar-displayed", true);
|
||||
document.querySelector("#runtime-listing-splitter").setAttribute("sidebar-displayed", true);
|
||||
},
|
||||
|
||||
showPopup: function() {
|
||||
let deferred = promise.defer();
|
||||
let panel = document.querySelector("#runtime-panel");
|
||||
let anchor = document.querySelector("#runtime-panel-button > .panel-button-anchor");
|
||||
|
||||
function onPopupShown() {
|
||||
panel.removeEventListener("popupshown", onPopupShown);
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
panel.addEventListener("popupshown", onPopupShown);
|
||||
panel.openPopup(anchor);
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
@ -15,17 +15,18 @@ const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
||||
const {AppProjects} = require("devtools/app-manager/app-projects");
|
||||
const {Connection} = require("devtools/client/connection-manager");
|
||||
const {AppManager} = require("devtools/webide/app-manager");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const ProjectEditor = require("projecteditor/projecteditor");
|
||||
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
|
||||
const {GetAvailableAddons} = require("devtools/webide/addons");
|
||||
const {getJSON} = require("devtools/shared/getjson");
|
||||
const utils = require("devtools/webide/utils");
|
||||
const Telemetry = require("devtools/shared/telemetry");
|
||||
const {RuntimeScanners, WiFiScanner} = require("devtools/webide/runtimes");
|
||||
const {RuntimeScanners} = require("devtools/webide/runtimes");
|
||||
const {showDoorhanger} = require("devtools/shared/doorhanger");
|
||||
const ProjectList = require("devtools/webide/project-list");
|
||||
const {Simulators} = require("devtools/webide/simulators");
|
||||
const RuntimeList = require("devtools/webide/runtime-list");
|
||||
|
||||
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
|
||||
|
||||
@ -56,6 +57,7 @@ window.addEventListener("unload", function onUnload() {
|
||||
});
|
||||
|
||||
let projectList;
|
||||
let runtimeList;
|
||||
|
||||
let UI = {
|
||||
init: function() {
|
||||
@ -71,17 +73,26 @@ let UI = {
|
||||
AppManager.on("app-manager-update", this.appManagerUpdate);
|
||||
|
||||
projectList = new ProjectList(window, window);
|
||||
ProjectPanel.toggle(projectList.sidebarsEnabled);
|
||||
if (projectList.sidebarsEnabled) {
|
||||
ProjectPanel.toggleSidebar();
|
||||
}
|
||||
runtimeList = new RuntimeList(window, window);
|
||||
if (runtimeList.sidebarsEnabled) {
|
||||
Cmds.showRuntimePanel();
|
||||
} else {
|
||||
runtimeList.update();
|
||||
}
|
||||
|
||||
this.updateCommands();
|
||||
this.updateRuntimeList();
|
||||
|
||||
this.onfocus = this.onfocus.bind(this);
|
||||
window.addEventListener("focus", this.onfocus, true);
|
||||
|
||||
AppProjects.load().then(() => {
|
||||
this.autoSelectProject();
|
||||
projectList.update();
|
||||
if (!projectList.sidebarsEnabled) {
|
||||
projectList.update();
|
||||
}
|
||||
}, e => {
|
||||
console.error(e);
|
||||
this.reportError("error_appProjectsLoadFailed");
|
||||
@ -129,7 +140,8 @@ let UI = {
|
||||
AppManager.off("app-manager-update", this.appManagerUpdate);
|
||||
AppManager.destroy();
|
||||
Simulators.off("configure", this.configureSimulator);
|
||||
projectList = null;
|
||||
projectList.destroy();
|
||||
runtimeList.destroy();
|
||||
window.removeEventListener("message", this.onMessage);
|
||||
this.updateConnectionTelemetry();
|
||||
this._telemetry.toolClosed("webide");
|
||||
@ -165,7 +177,6 @@ let UI = {
|
||||
// See AppManager.update() for descriptions of what these events mean.
|
||||
switch (what) {
|
||||
case "runtime-list":
|
||||
this.updateRuntimeList();
|
||||
this.autoConnectRuntime();
|
||||
break;
|
||||
case "connection":
|
||||
@ -188,22 +199,19 @@ let UI = {
|
||||
yield UI.autoStartProject();
|
||||
UI.autoOpenToolbox();
|
||||
UI.saveLastSelectedProject();
|
||||
projectList.update();
|
||||
UI.updateRemoveProjectButton();
|
||||
});
|
||||
return;
|
||||
case "project-started":
|
||||
this.updateCommands();
|
||||
projectList.update();
|
||||
UI.autoOpenToolbox();
|
||||
break;
|
||||
case "project-stopped":
|
||||
UI.destroyToolbox();
|
||||
this.updateCommands();
|
||||
projectList.update();
|
||||
break;
|
||||
case "runtime-global-actors":
|
||||
this.updateCommands();
|
||||
projectList.update();
|
||||
break;
|
||||
case "runtime-details":
|
||||
this.updateRuntimeButton();
|
||||
@ -217,17 +225,12 @@ let UI = {
|
||||
this.updateCommands();
|
||||
this.updateProjectButton();
|
||||
this.updateProjectEditorHeader();
|
||||
projectList.update();
|
||||
break;
|
||||
case "project-removed":
|
||||
projectList.update();
|
||||
break;
|
||||
case "install-progress":
|
||||
this.updateProgress(Math.round(100 * details.bytesSent / details.totalBytes));
|
||||
break;
|
||||
case "runtime-targets":
|
||||
this.autoSelectProject();
|
||||
projectList.update(details);
|
||||
break;
|
||||
case "pre-package":
|
||||
this.prePackageLog(details);
|
||||
@ -286,17 +289,19 @@ let UI = {
|
||||
busy: function() {
|
||||
this.hidePanels();
|
||||
let win = document.querySelector("window");
|
||||
win.classList.add("busy")
|
||||
win.classList.add("busy");
|
||||
win.classList.add("busy-undetermined");
|
||||
this.updateCommands();
|
||||
this.update("busy");
|
||||
},
|
||||
|
||||
unbusy: function() {
|
||||
let win = document.querySelector("window");
|
||||
win.classList.remove("busy")
|
||||
win.classList.remove("busy");
|
||||
win.classList.remove("busy-determined");
|
||||
win.classList.remove("busy-undetermined");
|
||||
this.updateCommands();
|
||||
this.update("unbusy");
|
||||
this._busyPromise = null;
|
||||
},
|
||||
|
||||
@ -384,75 +389,95 @@ let UI = {
|
||||
nbox.removeAllNotifications(true);
|
||||
},
|
||||
|
||||
/********** RUNTIME **********/
|
||||
/********** COMMANDS **********/
|
||||
|
||||
updateRuntimeList: function() {
|
||||
let wifiHeaderNode = document.querySelector("#runtime-header-wifi");
|
||||
if (WiFiScanner.allowed) {
|
||||
wifiHeaderNode.removeAttribute("hidden");
|
||||
} else {
|
||||
wifiHeaderNode.setAttribute("hidden", "true");
|
||||
/**
|
||||
* This module emits various events when state changes occur.
|
||||
*
|
||||
* The events this module may emit include:
|
||||
* busy:
|
||||
* The window is currently busy and certain UI functions may be disabled.
|
||||
* unbusy:
|
||||
* The window is not busy and certain UI functions may be re-enabled.
|
||||
*/
|
||||
update: function(what, details) {
|
||||
this.emit("webide-update", what, details);
|
||||
},
|
||||
|
||||
updateCommands: function() {
|
||||
// Action commands
|
||||
let playCmd = document.querySelector("#cmd_play");
|
||||
let stopCmd = document.querySelector("#cmd_stop");
|
||||
let debugCmd = document.querySelector("#cmd_toggleToolbox");
|
||||
let playButton = document.querySelector('#action-button-play');
|
||||
let projectPanelCmd = document.querySelector("#cmd_showProjectPanel");
|
||||
|
||||
if (document.querySelector("window").classList.contains("busy")) {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
debugCmd.setAttribute("disabled", "true");
|
||||
projectPanelCmd.setAttribute("disabled", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
let usbListNode = document.querySelector("#runtime-panel-usb");
|
||||
let wifiListNode = document.querySelector("#runtime-panel-wifi");
|
||||
let simulatorListNode = document.querySelector("#runtime-panel-simulator");
|
||||
let otherListNode = document.querySelector("#runtime-panel-other");
|
||||
|
||||
let noHelperNode = document.querySelector("#runtime-panel-noadbhelper");
|
||||
let noUSBNode = document.querySelector("#runtime-panel-nousbdevice");
|
||||
|
||||
if (Devices.helperAddonInstalled) {
|
||||
noHelperNode.setAttribute("hidden", "true");
|
||||
if (!AppManager.selectedProject || !AppManager.connected) {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
debugCmd.setAttribute("disabled", "true");
|
||||
} else {
|
||||
noHelperNode.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
let runtimeList = AppManager.runtimeList;
|
||||
|
||||
if (runtimeList.usb.length === 0 && Devices.helperAddonInstalled) {
|
||||
noUSBNode.removeAttribute("hidden");
|
||||
} else {
|
||||
noUSBNode.setAttribute("hidden", "true");
|
||||
}
|
||||
|
||||
for (let [type, parent] of [
|
||||
["usb", usbListNode],
|
||||
["wifi", wifiListNode],
|
||||
["simulator", simulatorListNode],
|
||||
["other", otherListNode],
|
||||
]) {
|
||||
while (parent.hasChildNodes()) {
|
||||
parent.firstChild.remove();
|
||||
let isProjectRunning = AppManager.isProjectRunning();
|
||||
if (isProjectRunning) {
|
||||
playButton.classList.add("reload");
|
||||
stopCmd.removeAttribute("disabled");
|
||||
debugCmd.removeAttribute("disabled");
|
||||
} else {
|
||||
playButton.classList.remove("reload");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
debugCmd.setAttribute("disabled", "true");
|
||||
}
|
||||
for (let runtime of runtimeList[type]) {
|
||||
let r = runtime;
|
||||
let panelItemNode = document.createElement("hbox");
|
||||
panelItemNode.className = "panel-item-complex";
|
||||
|
||||
let connectButton = document.createElement("toolbarbutton");
|
||||
connectButton.className = "panel-item runtime-panel-item-" + type;
|
||||
connectButton.setAttribute("label", r.name);
|
||||
connectButton.setAttribute("flex", "1");
|
||||
connectButton.addEventListener("click", () => {
|
||||
this.hidePanels();
|
||||
this.dismissErrorNotification();
|
||||
this.connectToRuntime(r);
|
||||
}, true);
|
||||
panelItemNode.appendChild(connectButton);
|
||||
|
||||
if (r.configure && UI.isRuntimeConfigurationEnabled()) {
|
||||
let configButton = document.createElement("toolbarbutton");
|
||||
configButton.className = "configure-button";
|
||||
configButton.addEventListener("click", r.configure.bind(r), true);
|
||||
panelItemNode.appendChild(configButton);
|
||||
// If connected and a project is selected
|
||||
if (AppManager.selectedProject.type == "runtimeApp") {
|
||||
playCmd.removeAttribute("disabled");
|
||||
} else if (AppManager.selectedProject.type == "tab") {
|
||||
playCmd.removeAttribute("disabled");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
} else if (AppManager.selectedProject.type == "mainProcess") {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
} else {
|
||||
if (AppManager.selectedProject.errorsCount == 0 &&
|
||||
AppManager.runtimeCanHandleApps()) {
|
||||
playCmd.removeAttribute("disabled");
|
||||
} else {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
parent.appendChild(panelItemNode);
|
||||
}
|
||||
}
|
||||
|
||||
let runtimePanelButton = document.querySelector("#runtime-panel-button");
|
||||
|
||||
if (AppManager.connected) {
|
||||
runtimePanelButton.setAttribute("active", "true");
|
||||
} else {
|
||||
runtimePanelButton.removeAttribute("active");
|
||||
}
|
||||
|
||||
projectPanelCmd.removeAttribute("disabled");
|
||||
},
|
||||
|
||||
updateRemoveProjectButton: function() {
|
||||
// Remove command
|
||||
let removeCmdNode = document.querySelector("#cmd_removeProject");
|
||||
if (AppManager.selectedProject) {
|
||||
removeCmdNode.removeAttribute("disabled");
|
||||
} else {
|
||||
removeCmdNode.setAttribute("disabled", "true");
|
||||
}
|
||||
},
|
||||
|
||||
/********** RUNTIME **********/
|
||||
|
||||
get lastConnectedRuntime() {
|
||||
return Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");
|
||||
},
|
||||
@ -531,6 +556,8 @@ let UI = {
|
||||
}
|
||||
},
|
||||
|
||||
/********** ACTIONS **********/
|
||||
|
||||
_actionsToLog: new Set(),
|
||||
|
||||
/**
|
||||
@ -869,121 +896,6 @@ let UI = {
|
||||
this.updateProjectEditorMenusVisibility();
|
||||
},
|
||||
|
||||
/********** COMMANDS **********/
|
||||
|
||||
updateCommands: function() {
|
||||
|
||||
if (document.querySelector("window").classList.contains("busy")) {
|
||||
document.querySelector("#cmd_newApp").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_importPackagedApp").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_importHostedApp").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_showProjectPanel").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_showRuntimePanel").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_removeProject").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_disconnectRuntime").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_showPermissionsTable").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_takeScreenshot").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_showRuntimeDetails").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_play").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_stop").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_toggleToolbox").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_showDevicePrefs").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_showSettings").setAttribute("disabled", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelector("#cmd_newApp").removeAttribute("disabled");
|
||||
document.querySelector("#cmd_importPackagedApp").removeAttribute("disabled");
|
||||
document.querySelector("#cmd_importHostedApp").removeAttribute("disabled");
|
||||
document.querySelector("#cmd_showProjectPanel").removeAttribute("disabled");
|
||||
document.querySelector("#cmd_showRuntimePanel").removeAttribute("disabled");
|
||||
|
||||
// Action commands
|
||||
let playCmd = document.querySelector("#cmd_play");
|
||||
let stopCmd = document.querySelector("#cmd_stop");
|
||||
let debugCmd = document.querySelector("#cmd_toggleToolbox");
|
||||
let playButton = document.querySelector('#action-button-play');
|
||||
|
||||
if (!AppManager.selectedProject || !AppManager.connected) {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
debugCmd.setAttribute("disabled", "true");
|
||||
} else {
|
||||
let isProjectRunning = AppManager.isProjectRunning();
|
||||
if (isProjectRunning) {
|
||||
playButton.classList.add("reload");
|
||||
stopCmd.removeAttribute("disabled");
|
||||
debugCmd.removeAttribute("disabled");
|
||||
} else {
|
||||
playButton.classList.remove("reload");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
debugCmd.setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
// If connected and a project is selected
|
||||
if (AppManager.selectedProject.type == "runtimeApp") {
|
||||
playCmd.removeAttribute("disabled");
|
||||
} else if (AppManager.selectedProject.type == "tab") {
|
||||
playCmd.removeAttribute("disabled");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
} else if (AppManager.selectedProject.type == "mainProcess") {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
} else {
|
||||
if (AppManager.selectedProject.errorsCount == 0 &&
|
||||
AppManager.runtimeCanHandleApps()) {
|
||||
playCmd.removeAttribute("disabled");
|
||||
} else {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove command
|
||||
let removeCmdNode = document.querySelector("#cmd_removeProject");
|
||||
if (AppManager.selectedProject) {
|
||||
removeCmdNode.removeAttribute("disabled");
|
||||
} else {
|
||||
removeCmdNode.setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
// Runtime commands
|
||||
let screenshotCmd = document.querySelector("#cmd_takeScreenshot");
|
||||
let permissionsCmd = document.querySelector("#cmd_showPermissionsTable");
|
||||
let detailsCmd = document.querySelector("#cmd_showRuntimeDetails");
|
||||
let disconnectCmd = document.querySelector("#cmd_disconnectRuntime");
|
||||
let devicePrefsCmd = document.querySelector("#cmd_showDevicePrefs");
|
||||
let settingsCmd = document.querySelector("#cmd_showSettings");
|
||||
|
||||
let box = document.querySelector("#runtime-actions");
|
||||
|
||||
let runtimePanelButton = document.querySelector("#runtime-panel-button");
|
||||
if (AppManager.connected) {
|
||||
if (AppManager.deviceFront) {
|
||||
detailsCmd.removeAttribute("disabled");
|
||||
permissionsCmd.removeAttribute("disabled");
|
||||
screenshotCmd.removeAttribute("disabled");
|
||||
}
|
||||
if (AppManager.preferenceFront) {
|
||||
devicePrefsCmd.removeAttribute("disabled");
|
||||
}
|
||||
if (AppManager.settingsFront) {
|
||||
settingsCmd.removeAttribute("disabled");
|
||||
}
|
||||
disconnectCmd.removeAttribute("disabled");
|
||||
runtimePanelButton.setAttribute("active", "true");
|
||||
} else {
|
||||
detailsCmd.setAttribute("disabled", "true");
|
||||
permissionsCmd.setAttribute("disabled", "true");
|
||||
screenshotCmd.setAttribute("disabled", "true");
|
||||
disconnectCmd.setAttribute("disabled", "true");
|
||||
devicePrefsCmd.setAttribute("disabled", "true");
|
||||
settingsCmd.setAttribute("disabled", "true");
|
||||
runtimePanelButton.removeAttribute("active");
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/********** TOOLBOX **********/
|
||||
|
||||
onMessage: function(event) {
|
||||
@ -1099,6 +1011,8 @@ let UI = {
|
||||
}
|
||||
};
|
||||
|
||||
EventEmitter.decorate(UI);
|
||||
|
||||
let Cmds = {
|
||||
quit: function() {
|
||||
if (UI.canCloseProject()) {
|
||||
@ -1126,7 +1040,11 @@ let Cmds = {
|
||||
},
|
||||
|
||||
showProjectPanel: function() {
|
||||
ProjectPanel.toggle(projectList.sidebarsEnabled, true);
|
||||
if (projectList.sidebarsEnabled) {
|
||||
ProjectPanel.toggleSidebar();
|
||||
} else {
|
||||
ProjectPanel.showPopup();
|
||||
}
|
||||
|
||||
// There are currently no available events to listen for when an unselected
|
||||
// tab navigates. Since we show every tab's location in the project menu,
|
||||
@ -1144,18 +1062,11 @@ let Cmds = {
|
||||
showRuntimePanel: function() {
|
||||
RuntimeScanners.scan();
|
||||
|
||||
let panel = document.querySelector("#runtime-panel");
|
||||
let anchor = document.querySelector("#runtime-panel-button > .panel-button-anchor");
|
||||
|
||||
let deferred = promise.defer();
|
||||
function onPopupShown() {
|
||||
panel.removeEventListener("popupshown", onPopupShown);
|
||||
deferred.resolve();
|
||||
if (runtimeList.sidebarsEnabled) {
|
||||
RuntimePanel.toggleSidebar();
|
||||
} else {
|
||||
RuntimePanel.showPopup();
|
||||
}
|
||||
panel.addEventListener("popupshown", onPopupShown);
|
||||
|
||||
panel.openPopup(anchor);
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
disconnectRuntime: function() {
|
||||
@ -1167,12 +1078,7 @@ let Cmds = {
|
||||
},
|
||||
|
||||
takeScreenshot: function() {
|
||||
return UI.busyUntil(AppManager.deviceFront.screenshotToDataURL().then(longstr => {
|
||||
return longstr.string().then(dataURL => {
|
||||
longstr.release().then(null, console.error);
|
||||
UI.openInBrowser(dataURL);
|
||||
});
|
||||
}), "taking screenshot");
|
||||
runtimeList.takeScreenshot();
|
||||
},
|
||||
|
||||
showPermissionsTable: function() {
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"></script>
|
||||
<script type="application/javascript" src="project-panel.js"></script>
|
||||
<script type="application/javascript" src="runtime-panel.js"></script>
|
||||
<script type="application/javascript" src="webide.js"></script>
|
||||
|
||||
<commandset id="mainCommandSet">
|
||||
@ -167,7 +168,7 @@
|
||||
|
||||
<!-- Runtime panel -->
|
||||
<panel id="runtime-panel" type="arrow" position="bottomcenter topright" consumeoutsideclicks="true" animate="false">
|
||||
<vbox flex="1">
|
||||
<vbox flex="1" id="runtime-panel-box">
|
||||
<label class="panel-header">&runtimePanel_usb;</label>
|
||||
<toolbarbutton class="panel-item" label="&runtimePanel_nousbdevice;" id="runtime-panel-nousbdevice" command="cmd_showTroubleShooting"/>
|
||||
<toolbarbutton class="panel-item" label="&runtimePanel_noadbhelper;" id="runtime-panel-noadbhelper" command="cmd_showAddons"/>
|
||||
@ -193,9 +194,9 @@
|
||||
</popupset>
|
||||
|
||||
<notificationbox flex="1" id="notificationbox">
|
||||
<hbox flex="1" id="deck-panels">
|
||||
<vbox id="project-listing-panel" class="project-listing" flex="1">
|
||||
<div id="project-listing-wrapper">
|
||||
<div flex="1" id="deck-panels">
|
||||
<vbox id="project-listing-panel" class="project-listing panel-list" flex="1">
|
||||
<div id="project-listing-wrapper" class="panel-list-wrapper">
|
||||
<iframe id="project-listing-panel-details" flex="1" src="project-listing.xhtml"/>
|
||||
</div>
|
||||
</vbox>
|
||||
@ -213,7 +214,13 @@
|
||||
<iframe id="deck-panel-logs" flex="1" src="logs.xhtml"/>
|
||||
<iframe id="deck-panel-simulator" flex="1" lazysrc="simulator.xhtml"/>
|
||||
</deck>
|
||||
</hbox>
|
||||
<splitter class="devtools-side-splitter" id="runtime-listing-splitter"/>
|
||||
<vbox id="runtime-listing-panel" class="runtime-listing panel-list" flex="1">
|
||||
<div id="runtime-listing-wrapper" class="panel-list-wrapper">
|
||||
<iframe id="runtime-listing-panel-details" flex="1" src="runtime-listing.xhtml"/>
|
||||
</div>
|
||||
</vbox>
|
||||
</div>
|
||||
<splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>
|
||||
<!-- toolbox iframe will be inserted here -->
|
||||
</notificationbox>
|
||||
|
@ -117,6 +117,8 @@ let AppManager = exports.AppManager = {
|
||||
* name, manifest details, etc.
|
||||
* runtime:
|
||||
* The selected runtime has changed.
|
||||
* runtime-apps-icons:
|
||||
* The list of URLs for the runtime app icons are available.
|
||||
* runtime-global-actors:
|
||||
* The list of global actors for the entire runtime (but not actors for a
|
||||
* specific tab or app) are now available, so we can test for features
|
||||
@ -131,7 +133,7 @@ let AppManager = exports.AppManager = {
|
||||
* The list of remote runtime targets available from the currently
|
||||
* connected runtime (such as tabs or apps) has changed, or any of the
|
||||
* user-visible details (like names) for the non-selected runtime targets
|
||||
* has changed. This event includes |type| in the details, to distguish
|
||||
* has changed. This event includes |type| in the details, to distinguish
|
||||
* "apps" and "tabs".
|
||||
*/
|
||||
update: function(what, details) {
|
||||
@ -186,7 +188,7 @@ let AppManager = exports.AppManager = {
|
||||
.then(() => {
|
||||
this.checkIfProjectIsRunning();
|
||||
this.update("runtime-targets", { type: "apps" });
|
||||
front.fetchIcons();
|
||||
front.fetchIcons().then(() => this.update("runtime-apps-icons"));
|
||||
});
|
||||
} else {
|
||||
this._listTabsResponse = response;
|
||||
|
@ -16,9 +16,9 @@ const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/
|
||||
|
||||
let ProjectList;
|
||||
|
||||
module.exports = ProjectList = function(window, parentWindow) {
|
||||
module.exports = ProjectList = function(win, parentWindow) {
|
||||
EventEmitter.decorate(this);
|
||||
this._doc = window.document;
|
||||
this._doc = win.document;
|
||||
this._UI = parentWindow.UI;
|
||||
this._parentWindow = parentWindow;
|
||||
this._panelNodeEl = "toolbarbutton";
|
||||
@ -28,9 +28,12 @@ module.exports = ProjectList = function(window, parentWindow) {
|
||||
this._panelNodeEl = "div";
|
||||
}
|
||||
|
||||
AppManager.init();
|
||||
this.onWebIDEUpdate = this.onWebIDEUpdate.bind(this);
|
||||
this._UI.on("webide-update", this.onWebIDEUpdate);
|
||||
|
||||
return this;
|
||||
AppManager.init();
|
||||
this.appManagerUpdate = this.appManagerUpdate.bind(this);
|
||||
AppManager.on("app-manager-update", this.appManagerUpdate);
|
||||
};
|
||||
|
||||
ProjectList.prototype = {
|
||||
@ -42,6 +45,29 @@ ProjectList.prototype = {
|
||||
return this._sidebarsEnabled;
|
||||
},
|
||||
|
||||
appManagerUpdate: function(event, what, details) {
|
||||
// Got a message from app-manager.js
|
||||
// See AppManager.update() for descriptions of what these events mean.
|
||||
switch (what) {
|
||||
case "project-removed":
|
||||
case "runtime-apps-icons":
|
||||
case "runtime-targets":
|
||||
case "connection":
|
||||
this.update(details);
|
||||
break;
|
||||
case "project":
|
||||
this.updateCommands();
|
||||
this.update(details);
|
||||
break;
|
||||
};
|
||||
},
|
||||
|
||||
onWebIDEUpdate: function(event, what, details) {
|
||||
if (what == "busy" || what == "unbusy") {
|
||||
this.updateCommands();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* testOptions: { chrome mochitest support
|
||||
* folder: nsIFile, where to store the app
|
||||
@ -103,7 +129,7 @@ ProjectList.prototype = {
|
||||
* }
|
||||
*/
|
||||
_renderProjectItem: function(opts) {
|
||||
if (this._sidebarsEnabled) {
|
||||
if (this._sidebarsEnabled && this._doc !== this._parentWindow.document) {
|
||||
let span = this._doc.createElement("span");
|
||||
span.textContent = opts.name;
|
||||
let icon = this._doc.createElement("img");
|
||||
@ -232,7 +258,7 @@ ProjectList.prototype = {
|
||||
this._renderProjectItem({
|
||||
panel: panelItemNode,
|
||||
name: app.manifest.name,
|
||||
icon: app.iconURL
|
||||
icon: app.iconURL || AppManager.DEFAULT_PROJECT_ICON
|
||||
});
|
||||
runtimeAppsNode.appendChild(panelItemNode);
|
||||
panelItemNode.addEventListener("click", () => {
|
||||
@ -242,7 +268,7 @@ ProjectList.prototype = {
|
||||
AppManager.selectedProject = {
|
||||
type: "runtimeApp",
|
||||
app: app.manifest,
|
||||
icon: app.iconURL,
|
||||
icon: app.iconURL || AppManager.DEFAULT_PROJECT_ICON,
|
||||
name: app.manifest.name
|
||||
};
|
||||
}, true);
|
||||
@ -251,6 +277,38 @@ ProjectList.prototype = {
|
||||
return promise.resolve();
|
||||
},
|
||||
|
||||
updateCommands: function() {
|
||||
let doc = this._doc;
|
||||
let newAppCmd;
|
||||
let packagedAppCmd;
|
||||
let hostedAppCmd;
|
||||
|
||||
if (this._sidebarsEnabled) {
|
||||
newAppCmd = doc.querySelector("#new-app");
|
||||
packagedAppCmd = doc.querySelector("#packaged-app");
|
||||
hostedAppCmd = doc.querySelector("#hosted-app");
|
||||
} else {
|
||||
newAppCmd = doc.querySelector("#cmd_newApp")
|
||||
packagedAppCmd = doc.querySelector("#cmd_importPackagedApp");
|
||||
hostedAppCmd = doc.querySelector("#cmd_importHostedApp");
|
||||
}
|
||||
|
||||
if (!newAppCmd || !packagedAppCmd || !hostedAppCmd) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._parentWindow.document.querySelector("window").classList.contains("busy")) {
|
||||
newAppCmd.setAttribute("disabled", "true");
|
||||
packagedAppCmd.setAttribute("disabled", "true");
|
||||
hostedAppCmd.setAttribute("disabled", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
newAppCmd.removeAttribute("disabled");
|
||||
packagedAppCmd.removeAttribute("disabled");
|
||||
hostedAppCmd.removeAttribute("disabled");
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger an update of the project and remote runtime list.
|
||||
* @param options object (optional)
|
||||
@ -323,5 +381,17 @@ ProjectList.prototype = {
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._doc = null;
|
||||
AppManager.off("app-manager-update", this.appManagerUpdate);
|
||||
if (this._sidebarsEnabled) {
|
||||
this._UI.off("webide-update", this.onWebIDEUpdate);
|
||||
}
|
||||
this._UI = null;
|
||||
this._parentWindow = null;
|
||||
this._panelNodeEl = null;
|
||||
this._sidebarsEnabled = null;
|
||||
}
|
||||
};
|
||||
|
232
browser/devtools/webide/modules/runtime-list.js
Normal file
232
browser/devtools/webide/modules/runtime-list.js
Normal file
@ -0,0 +1,232 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
|
||||
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
||||
const {AppProjects} = require("devtools/app-manager/app-projects");
|
||||
const {AppManager} = require("devtools/webide/app-manager");
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const {RuntimeScanners, WiFiScanner} = require("devtools/webide/runtimes");
|
||||
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const utils = require("devtools/webide/utils");
|
||||
|
||||
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
|
||||
|
||||
let RuntimeList;
|
||||
|
||||
module.exports = RuntimeList = function(window, parentWindow) {
|
||||
EventEmitter.decorate(this);
|
||||
this._doc = window.document;
|
||||
this._UI = parentWindow.UI;
|
||||
this._Cmds = parentWindow.Cmds;
|
||||
this._parentWindow = parentWindow;
|
||||
this._panelNodeEl = "toolbarbutton";
|
||||
this._panelBoxEl = "hbox";
|
||||
this._sidebarsEnabled = Services.prefs.getBoolPref("devtools.webide.sidebars");
|
||||
|
||||
if (this._sidebarsEnabled) {
|
||||
this._panelNodeEl = "button";
|
||||
this._panelBoxEl = "div";
|
||||
}
|
||||
|
||||
this.onWebIDEUpdate = this.onWebIDEUpdate.bind(this);
|
||||
this._UI.on("webide-update", this.onWebIDEUpdate);
|
||||
|
||||
AppManager.init();
|
||||
this.appManagerUpdate = this.appManagerUpdate.bind(this);
|
||||
AppManager.on("app-manager-update", this.appManagerUpdate);
|
||||
};
|
||||
|
||||
RuntimeList.prototype = {
|
||||
get doc() {
|
||||
return this._doc;
|
||||
},
|
||||
|
||||
get sidebarsEnabled() {
|
||||
return this._sidebarsEnabled;
|
||||
},
|
||||
|
||||
appManagerUpdate: function(event, what, details) {
|
||||
// Got a message from app-manager.js
|
||||
// See AppManager.update() for descriptions of what these events mean.
|
||||
switch (what) {
|
||||
case "runtime-list":
|
||||
this.update();
|
||||
break;
|
||||
case "connection":
|
||||
case "runtime-global-actors":
|
||||
this.updateCommands();
|
||||
break;
|
||||
};
|
||||
},
|
||||
|
||||
onWebIDEUpdate: function(event, what, details) {
|
||||
if (what == "busy" || what == "unbusy") {
|
||||
this.updateCommands();
|
||||
}
|
||||
},
|
||||
|
||||
takeScreenshot: function() {
|
||||
return this._UI.busyUntil(AppManager.deviceFront.screenshotToDataURL().then(longstr => {
|
||||
return longstr.string().then(dataURL => {
|
||||
longstr.release().then(null, console.error);
|
||||
this._UI.openInBrowser(dataURL);
|
||||
});
|
||||
}), "taking screenshot");
|
||||
},
|
||||
|
||||
showRuntimeDetails: function() {
|
||||
this._Cmds.showRuntimeDetails();
|
||||
},
|
||||
|
||||
showPermissionsTable: function() {
|
||||
this._Cmds.showPermissionsTable();
|
||||
},
|
||||
|
||||
showDevicePreferences: function() {
|
||||
this._Cmds.showDevicePrefs();
|
||||
},
|
||||
|
||||
showSettings: function() {
|
||||
this._Cmds.showSettings();
|
||||
},
|
||||
|
||||
showTroubleShooting: function() {
|
||||
this._Cmds.showTroubleShooting();
|
||||
},
|
||||
|
||||
showAddons: function() {
|
||||
this._Cmds.showAddons();
|
||||
},
|
||||
|
||||
updateCommands: function() {
|
||||
let doc = this._doc;
|
||||
|
||||
// Runtime commands
|
||||
let screenshotCmd = doc.querySelector("#runtime-screenshot");
|
||||
let permissionsCmd = doc.querySelector("#runtime-permissions");
|
||||
let detailsCmd = doc.querySelector("#runtime-details");
|
||||
let disconnectCmd = doc.querySelector("#runtime-disconnect");
|
||||
let devicePrefsCmd = doc.querySelector("#runtime-preferences");
|
||||
let settingsCmd = doc.querySelector("#runtime-settings");
|
||||
|
||||
if (AppManager.connected) {
|
||||
if (AppManager.deviceFront) {
|
||||
detailsCmd.removeAttribute("disabled");
|
||||
permissionsCmd.removeAttribute("disabled");
|
||||
screenshotCmd.removeAttribute("disabled");
|
||||
}
|
||||
if (AppManager.preferenceFront) {
|
||||
devicePrefsCmd.removeAttribute("disabled");
|
||||
}
|
||||
if (AppManager.settingsFront) {
|
||||
settingsCmd.removeAttribute("disabled");
|
||||
}
|
||||
disconnectCmd.removeAttribute("disabled");
|
||||
} else {
|
||||
detailsCmd.setAttribute("disabled", "true");
|
||||
permissionsCmd.setAttribute("disabled", "true");
|
||||
screenshotCmd.setAttribute("disabled", "true");
|
||||
disconnectCmd.setAttribute("disabled", "true");
|
||||
devicePrefsCmd.setAttribute("disabled", "true");
|
||||
settingsCmd.setAttribute("disabled", "true");
|
||||
}
|
||||
},
|
||||
|
||||
update: function() {
|
||||
let doc = this._doc;
|
||||
let wifiHeaderNode = doc.querySelector("#runtime-header-wifi");
|
||||
|
||||
if (WiFiScanner.allowed) {
|
||||
wifiHeaderNode.removeAttribute("hidden");
|
||||
} else {
|
||||
wifiHeaderNode.setAttribute("hidden", "true");
|
||||
}
|
||||
|
||||
let usbListNode = doc.querySelector("#runtime-panel-usb");
|
||||
let wifiListNode = doc.querySelector("#runtime-panel-wifi");
|
||||
let simulatorListNode = doc.querySelector("#runtime-panel-simulator");
|
||||
let otherListNode = doc.querySelector("#runtime-panel-other");
|
||||
let noHelperNode = doc.querySelector("#runtime-panel-noadbhelper");
|
||||
let noUSBNode = doc.querySelector("#runtime-panel-nousbdevice");
|
||||
|
||||
if (Devices.helperAddonInstalled) {
|
||||
noHelperNode.setAttribute("hidden", "true");
|
||||
} else {
|
||||
noHelperNode.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
let runtimeList = AppManager.runtimeList;
|
||||
|
||||
if (!runtimeList) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (runtimeList.usb.length === 0 && Devices.helperAddonInstalled) {
|
||||
noUSBNode.removeAttribute("hidden");
|
||||
} else {
|
||||
noUSBNode.setAttribute("hidden", "true");
|
||||
}
|
||||
|
||||
for (let [type, parent] of [
|
||||
["usb", usbListNode],
|
||||
["wifi", wifiListNode],
|
||||
["simulator", simulatorListNode],
|
||||
["other", otherListNode],
|
||||
]) {
|
||||
while (parent.hasChildNodes()) {
|
||||
parent.firstChild.remove();
|
||||
}
|
||||
for (let runtime of runtimeList[type]) {
|
||||
let r = runtime;
|
||||
let panelItemNode = doc.createElement(this._panelBoxEl);
|
||||
panelItemNode.className = "panel-item-complex";
|
||||
|
||||
let connectButton = doc.createElement(this._panelNodeEl);
|
||||
connectButton.className = "panel-item runtime-panel-item-" + type;
|
||||
|
||||
if (this._sidebarsEnabled) {
|
||||
connectButton.textContent = r.name;
|
||||
} else {
|
||||
connectButton.setAttribute("label", r.name);
|
||||
connectButton.setAttribute("flex", "1");
|
||||
}
|
||||
|
||||
connectButton.addEventListener("click", () => {
|
||||
if (!this._sidebarsEnabled) {
|
||||
this._UI.hidePanels();
|
||||
}
|
||||
this._UI.dismissErrorNotification();
|
||||
this._UI.connectToRuntime(r);
|
||||
}, true);
|
||||
panelItemNode.appendChild(connectButton);
|
||||
|
||||
if (r.configure && this._UI.isRuntimeConfigurationEnabled()) {
|
||||
let configButton = doc.createElement(this._panelNodeEl);
|
||||
configButton.className = "configure-button";
|
||||
configButton.addEventListener("click", r.configure.bind(r), true);
|
||||
panelItemNode.appendChild(configButton);
|
||||
}
|
||||
|
||||
parent.appendChild(panelItemNode);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._doc = null;
|
||||
AppManager.off("app-manager-update", this.appManagerUpdate);
|
||||
if (this.sidebarsEnabled) {
|
||||
this._UI.off("webide-update", this.onWebIDEUpdate);
|
||||
}
|
||||
this._UI = null;
|
||||
this._Cmds = null;
|
||||
this._parentWindow = null;
|
||||
this._panelNodeEl = null;
|
||||
this._sidebarsEnabled = null;
|
||||
}
|
||||
};
|
@ -25,6 +25,7 @@ EXTRA_JS_MODULES.devtools.webide += [
|
||||
'modules/build.js',
|
||||
'modules/config-view.js',
|
||||
'modules/project-list.js',
|
||||
'modules/runtime-list.js',
|
||||
'modules/runtimes.js',
|
||||
'modules/simulator-process.js',
|
||||
'modules/simulators.js',
|
||||
|
@ -6,7 +6,7 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webide/test/doc_ta
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
SimpleTest.requestCompleteLog();
|
||||
requestCompleteLog();
|
||||
|
||||
Task.spawn(function() {
|
||||
const { DebuggerServer } =
|
||||
@ -39,6 +39,7 @@ function test() {
|
||||
let tabsNode = win.document.querySelector("#project-panel-tabs");
|
||||
is(tabsNode.querySelectorAll(".panel-item").length, 2, "2 tabs available");
|
||||
yield removeTab(tab);
|
||||
yield waitForUpdate(win, "project");
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
is(tabsNode.querySelectorAll(".panel-item").length, 1, "1 tab available");
|
||||
|
||||
|
@ -34,7 +34,9 @@ Services.prefs.setCharPref("devtools.webide.adaptersAddonURL", TEST_BASE + "addo
|
||||
Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
|
||||
Services.prefs.setCharPref("devtools.devices.url", TEST_BASE + "browser_devices.json");
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
let registerCleanupFunction = registerCleanupFunction ||
|
||||
SimpleTest.registerCleanupFunction;
|
||||
registerCleanupFunction(() => {
|
||||
gDevTools.testing = false;
|
||||
Services.prefs.clearUserPref("devtools.webide.enabled");
|
||||
Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
|
||||
@ -194,10 +196,29 @@ function removeTab(aTab, aWindow) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getRuntimeDocument(win) {
|
||||
return win.document.querySelector("#runtime-listing-panel-details").contentDocument;
|
||||
}
|
||||
|
||||
function getProjectDocument(win) {
|
||||
return win.document.querySelector("#project-listing-panel-details").contentDocument;
|
||||
}
|
||||
|
||||
function connectToLocalRuntime(aWindow) {
|
||||
info("Loading local runtime.");
|
||||
|
||||
let panelNode = aWindow.document.querySelector("#runtime-panel");
|
||||
let panelNode;
|
||||
let runtimePanel;
|
||||
|
||||
// If we are currently toggled to the sidebar panel display in WebIDE then
|
||||
// the runtime panel is in an iframe.
|
||||
if (aWindow.runtimeList.sidebarsEnabled) {
|
||||
runtimePanel = getRuntimeDocument(aWindow);
|
||||
} else {
|
||||
runtimePanel = aWindow.document;
|
||||
}
|
||||
|
||||
panelNode = runtimePanel.querySelector("#runtime-panel");
|
||||
let items = panelNode.querySelectorAll(".runtime-panel-item-other");
|
||||
is(items.length, 2, "Found 2 custom runtime buttons");
|
||||
|
||||
|
@ -2,4 +2,8 @@
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
../doc_tabs.html
|
||||
../head.js
|
||||
|
||||
[browser_tabs.js]
|
||||
skip-if = e10s # Bug 1072167 - browser_tabs.js test fails under e10s
|
||||
|
75
browser/devtools/webide/test/sidebars/browser_tabs.js
Normal file
75
browser/devtools/webide/test/sidebars/browser_tabs.js
Normal file
@ -0,0 +1,75 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webide/test/doc_tabs.html";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
requestCompleteLog();
|
||||
|
||||
Task.spawn(function() {
|
||||
const { DebuggerServer } =
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
|
||||
// Since we test the connections set below, destroy the server in case it
|
||||
// was left open.
|
||||
DebuggerServer.destroy();
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
|
||||
let tab = yield addTab(TEST_URI);
|
||||
|
||||
let win = yield openWebIDE();
|
||||
let docProject = getProjectDocument(win);
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
|
||||
yield connectToLocal(win, docRuntime);
|
||||
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
|
||||
|
||||
yield selectTabProject(win, docProject);
|
||||
|
||||
ok(win.UI.toolboxPromise, "Toolbox promise exists");
|
||||
yield win.UI.toolboxPromise;
|
||||
|
||||
let project = win.AppManager.selectedProject;
|
||||
is(project.location, TEST_URI, "Location is correct");
|
||||
is(project.name, "example.com: Test Tab", "Name is correct");
|
||||
|
||||
// Ensure tab list changes are noticed
|
||||
let tabsNode = docProject.querySelector("#project-panel-tabs");
|
||||
is(tabsNode.querySelectorAll(".panel-item").length, 2, "2 tabs available");
|
||||
yield removeTab(tab);
|
||||
yield waitForUpdate(win, "project");
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
is(tabsNode.querySelectorAll(".panel-item").length, 1, "1 tab available");
|
||||
|
||||
yield win.Cmds.disconnectRuntime();
|
||||
yield closeWebIDE(win);
|
||||
|
||||
DebuggerServer.destroy();
|
||||
}).then(finish, handleError);
|
||||
}
|
||||
|
||||
function connectToLocal(win, docRuntime) {
|
||||
let deferred = promise.defer();
|
||||
win.AppManager.connection.once(
|
||||
win.Connection.Events.CONNECTED,
|
||||
() => deferred.resolve());
|
||||
docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function selectTabProject(win, docProject) {
|
||||
return Task.spawn(function() {
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
let tabsNode = docProject.querySelector("#project-panel-tabs");
|
||||
let tabNode = tabsNode.querySelectorAll(".panel-item")[1];
|
||||
let project = waitForUpdate(win, "project");
|
||||
tabNode.click();
|
||||
yield project;
|
||||
});
|
||||
}
|
@ -1,9 +1,22 @@
|
||||
[DEFAULT]
|
||||
tags = devtools
|
||||
skip-if = buildapp == 'b2g'
|
||||
support-files =
|
||||
../app/index.html
|
||||
../app/manifest.webapp
|
||||
../head.js
|
||||
../device_front_shared.js
|
||||
|
||||
[test_duplicate_import.html]
|
||||
[test_newapp.html]
|
||||
[test_import.html]
|
||||
[test_duplicate_import.html]
|
||||
[test_runtime.html]
|
||||
[test_manifestUpdate.html]
|
||||
[test_addons.html]
|
||||
[test_autoconnect_runtime.html]
|
||||
[test_autoselect_project.html]
|
||||
[test_fullscreenToolbox.html]
|
||||
[test_device_preferences.html]
|
||||
[test_device_permissions.html]
|
||||
[test_device_settings.html]
|
||||
[test_telemetry.html]
|
||||
|
178
browser/devtools/webide/test/sidebars/test_addons.html
Normal file
178
browser/devtools/webide/test/sidebars/test_addons.html
Normal file
@ -0,0 +1,178 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const {GetAvailableAddons} = require("devtools/webide/addons");
|
||||
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
|
||||
const {Simulators} = require("devtools/webide/simulators");
|
||||
|
||||
let adbAddonsInstalled = promise.defer();
|
||||
Devices.on("addon-status-updated", function onUpdate1() {
|
||||
Devices.off("addon-status-updated", onUpdate1);
|
||||
adbAddonsInstalled.resolve();
|
||||
});
|
||||
|
||||
function getVersion(name) {
|
||||
return name.match(/(\d+\.\d+)/)[0];
|
||||
}
|
||||
|
||||
function onSimulatorInstalled(name) {
|
||||
let deferred = promise.defer();
|
||||
Simulators.on("updated", function onUpdate() {
|
||||
Simulators.findSimulatorAddons().then(addons => {
|
||||
for (let addon of addons) {
|
||||
if (name == addon.name.replace(" Simulator", "")) {
|
||||
Simulators.off("updated", onUpdate);
|
||||
nextTick().then(deferred.resolve);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function installSimulatorFromUI(doc, name) {
|
||||
let li = doc.querySelector('[addon="simulator-' + getVersion(name) + '"]');
|
||||
li.querySelector(".install-button").click();
|
||||
return onSimulatorInstalled(name);
|
||||
}
|
||||
|
||||
function uninstallSimulatorFromUI(doc, name) {
|
||||
let deferred = promise.defer();
|
||||
Simulators.on("updated", function onUpdate() {
|
||||
nextTick().then(() => {
|
||||
let li = doc.querySelector('[status="uninstalled"][addon="simulator-' + getVersion(name) + '"]');
|
||||
if (li) {
|
||||
Simulators.off("updated", onUpdate);
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject("Can't find item");
|
||||
}
|
||||
});
|
||||
});
|
||||
let li = doc.querySelector('[status="installed"][addon="simulator-' + getVersion(name) + '"]');
|
||||
li.querySelector(".uninstall-button").click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function uninstallADBFromUI(doc) {
|
||||
let deferred = promise.defer();
|
||||
Devices.on("addon-status-updated", function onUpdate() {
|
||||
nextTick().then(() => {
|
||||
let li = doc.querySelector('[status="uninstalled"][addon="adb"]');
|
||||
if (li) {
|
||||
Devices.off("addon-status-updated", onUpdate);
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject("Can't find item");
|
||||
}
|
||||
})
|
||||
});
|
||||
let li = doc.querySelector('[status="installed"][addon="adb"]');
|
||||
li.querySelector(".uninstall-button").click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
Task.spawn(function*() {
|
||||
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
|
||||
ok(!Devices.helperAddonInstalled, "Helper not installed");
|
||||
|
||||
let win = yield openWebIDE(true);
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
|
||||
yield adbAddonsInstalled.promise;
|
||||
|
||||
ok(Devices.helperAddonInstalled, "Helper has been auto-installed");
|
||||
|
||||
yield nextTick();
|
||||
|
||||
let addons = yield GetAvailableAddons();
|
||||
|
||||
is(addons.simulators.length, 3, "3 simulator addons to install");
|
||||
|
||||
let sim10 = addons.simulators.filter(a => a.version == "1.0")[0];
|
||||
sim10.install();
|
||||
|
||||
yield onSimulatorInstalled("Firefox OS 1.0");
|
||||
|
||||
win.Cmds.showAddons();
|
||||
|
||||
let frame = win.document.querySelector("#deck-panel-addons");
|
||||
let addonDoc = frame.contentWindow.document;
|
||||
let lis;
|
||||
|
||||
lis = addonDoc.querySelectorAll("li");
|
||||
is(lis.length, 5, "5 addons listed");
|
||||
|
||||
lis = addonDoc.querySelectorAll('li[status="installed"]');
|
||||
is(lis.length, 3, "3 addons installed");
|
||||
|
||||
lis = addonDoc.querySelectorAll('li[status="uninstalled"]');
|
||||
is(lis.length, 2, "2 addons uninstalled");
|
||||
|
||||
info("Uninstalling Simulator 2.0");
|
||||
|
||||
yield installSimulatorFromUI(addonDoc, "Firefox OS 2.0");
|
||||
|
||||
info("Uninstalling Simulator 3.0");
|
||||
|
||||
yield installSimulatorFromUI(addonDoc, "Firefox OS 3.0");
|
||||
|
||||
yield nextTick();
|
||||
|
||||
let panelNode = docRuntime.querySelector("#runtime-panel");
|
||||
let items;
|
||||
|
||||
items = panelNode.querySelectorAll(".runtime-panel-item-usb");
|
||||
is(items.length, 1, "Found one runtime button");
|
||||
|
||||
items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
|
||||
is(items.length, 3, "Found 3 simulators button");
|
||||
|
||||
yield uninstallSimulatorFromUI(addonDoc, "Firefox OS 1.0");
|
||||
yield uninstallSimulatorFromUI(addonDoc, "Firefox OS 2.0");
|
||||
yield uninstallSimulatorFromUI(addonDoc, "Firefox OS 3.0");
|
||||
|
||||
items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
|
||||
is(items.length, 0, "No simulator listed");
|
||||
|
||||
let w = addonDoc.querySelector(".warning");
|
||||
let display = addonDoc.defaultView.getComputedStyle(w).display
|
||||
is(display, "none", "Warning about missing ADB hidden");
|
||||
|
||||
yield uninstallADBFromUI(addonDoc, "adb");
|
||||
|
||||
items = panelNode.querySelectorAll(".runtime-panel-item-usb");
|
||||
is(items.length, 0, "No usb runtime listed");
|
||||
|
||||
display = addonDoc.defaultView.getComputedStyle(w).display
|
||||
is(display, "block", "Warning about missing ADB present");
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
SimpleTest.finish();
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function*() {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let win = yield openWebIDE();
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
|
||||
let fakeRuntime = {
|
||||
type: "USB",
|
||||
connect: function(connection) {
|
||||
is(connection, win.AppManager.connection, "connection is valid");
|
||||
connection.host = null; // force connectPipe
|
||||
connection.connect();
|
||||
return promise.resolve();
|
||||
},
|
||||
|
||||
get id() {
|
||||
return "fakeRuntime";
|
||||
},
|
||||
|
||||
get name() {
|
||||
return "fakeRuntime";
|
||||
}
|
||||
};
|
||||
win.AppManager.runtimeList.usb.push(fakeRuntime);
|
||||
win.AppManager.update("runtime-list");
|
||||
|
||||
let panelNode = docRuntime.querySelector("#runtime-panel");
|
||||
let items = panelNode.querySelectorAll(".runtime-panel-item-usb");
|
||||
is(items.length, 1, "Found one runtime button");
|
||||
|
||||
let deferred = promise.defer();
|
||||
win.AppManager.connection.once(
|
||||
win.Connection.Events.CONNECTED,
|
||||
() => deferred.resolve());
|
||||
items[0].click();
|
||||
|
||||
ok(win.document.querySelector("window").className, "busy", "UI is busy");
|
||||
yield win.UI._busyPromise;
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
|
||||
|
||||
yield nextTick();
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected");
|
||||
|
||||
win = yield openWebIDE();
|
||||
|
||||
win.AppManager.runtimeList.usb.push(fakeRuntime);
|
||||
win.AppManager.update("runtime-list");
|
||||
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Automatically reconnected");
|
||||
|
||||
yield win.Cmds.disconnectRuntime();
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
DebuggerServer.destroy();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let win = yield openWebIDE();
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
let docProject = getProjectDocument(win);
|
||||
|
||||
let panelNode = docRuntime.querySelector("#runtime-panel");
|
||||
let items = panelNode.querySelectorAll(".runtime-panel-item-other");
|
||||
is(items.length, 2, "Found 2 runtime buttons");
|
||||
|
||||
// Connect to local runtime
|
||||
items[1].click();
|
||||
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
|
||||
|
||||
ok(win.AppManager.isMainProcessDebuggable(), "Main process available");
|
||||
|
||||
// Select main process
|
||||
yield win.Cmds.showProjectPanel();
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
SimpleTest.executeSoon(() => {
|
||||
docProject.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click();
|
||||
});
|
||||
|
||||
yield waitForUpdate(win, "project");
|
||||
|
||||
let lastProject = Services.prefs.getCharPref("devtools.webide.lastSelectedProject");
|
||||
is(lastProject, "mainProcess:", "Last project is main process");
|
||||
|
||||
yield nextTick();
|
||||
yield closeWebIDE(win);
|
||||
|
||||
is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected");
|
||||
|
||||
// Re-open, should reselect main process after connection
|
||||
win = yield openWebIDE();
|
||||
|
||||
docRuntime = getRuntimeDocument(win);
|
||||
|
||||
panelNode = docRuntime.querySelector("#runtime-panel");
|
||||
items = panelNode.querySelectorAll(".runtime-panel-item-other");
|
||||
is(items.length, 2, "Found 2 runtime buttons");
|
||||
|
||||
// Connect to local runtime
|
||||
items[1].click();
|
||||
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
|
||||
ok(win.AppManager.isMainProcessDebuggable(), "Main process available");
|
||||
is(win.AppManager.selectedProject.type, "mainProcess", "Main process reselected");
|
||||
|
||||
yield win.Cmds.disconnectRuntime();
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
DebuggerServer.destroy();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let win = yield openWebIDE();
|
||||
|
||||
let permIframe = win.document.querySelector("#deck-panel-permissionstable");
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
|
||||
yield connectToLocalRuntime(win);
|
||||
|
||||
let perm = docRuntime.querySelector("#runtime-permissions");
|
||||
|
||||
ok(!perm.hasAttribute("disabled"), "perm cmd enabled");
|
||||
|
||||
let deck = win.document.querySelector("#deck");
|
||||
|
||||
win.Cmds.showPermissionsTable();
|
||||
is(deck.selectedPanel, permIframe, "permission iframe selected");
|
||||
|
||||
yield nextTick();
|
||||
|
||||
yield lazyIframeIsLoaded(permIframe);
|
||||
|
||||
yield permIframe.contentWindow.getRawPermissionsTablePromise;
|
||||
|
||||
doc = permIframe.contentWindow.document;
|
||||
trs = doc.querySelectorAll(".line");
|
||||
found = false;
|
||||
for (let tr of trs) {
|
||||
let [name,v1,v2,v3] = tr.querySelectorAll("td");
|
||||
if (name.textContent == "geolocation") {
|
||||
found = true;
|
||||
is(v1.className, "permprompt", "geolocation perm is valid");
|
||||
is(v2.className, "permprompt", "geolocation perm is valid");
|
||||
is(v3.className, "permprompt", "geolocation perm is valid");
|
||||
break;
|
||||
}
|
||||
}
|
||||
ok(found, "Found geolocation line");
|
||||
|
||||
doc.querySelector("#close").click();
|
||||
|
||||
ok(!deck.selectedPanel, "No panel selected");
|
||||
|
||||
DebuggerServer.destroy();
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
SimpleTest.finish();
|
||||
}).then(null, e => {
|
||||
ok(false, "Exception: " + e);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../head.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../device_front_shared.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let win = yield openWebIDE();
|
||||
|
||||
let prefIframe = win.document.querySelector("#deck-panel-devicepreferences");
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
|
||||
win.AppManager.update("runtime-list");
|
||||
|
||||
yield connectToLocalRuntime(win);
|
||||
|
||||
let prefs = docRuntime.querySelector("#runtime-preferences");
|
||||
|
||||
ok(!prefs.hasAttribute("disabled"), "device prefs cmd enabled");
|
||||
|
||||
let deck = win.document.querySelector("#deck");
|
||||
|
||||
win.Cmds.showDevicePrefs();
|
||||
is(deck.selectedPanel, prefIframe, "device preferences iframe selected");
|
||||
|
||||
yield nextTick();
|
||||
|
||||
yield lazyIframeIsLoaded(prefIframe);
|
||||
|
||||
yield prefIframe.contentWindow.getAllPrefs;
|
||||
|
||||
setDocument(prefIframe);
|
||||
|
||||
let fields = doc.querySelectorAll(".editable");
|
||||
|
||||
addNewField();
|
||||
|
||||
let preference = "accessibility.accesskeycausesactivation";
|
||||
|
||||
fieldChange(fields, preference);
|
||||
|
||||
addNewFieldWithEnter();
|
||||
|
||||
editExistingField();
|
||||
|
||||
addNewFieldInteger();
|
||||
|
||||
yield editFieldInteger();
|
||||
|
||||
yield resetExistingField("accessibility.accesskeycausesactivation");
|
||||
|
||||
addNewFieldBoolean();
|
||||
|
||||
searchFields(deck, "debugger");
|
||||
|
||||
DebuggerServer.destroy();
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
SimpleTest.finish();
|
||||
}).then(null, e => {
|
||||
ok(false, "Exception: " + e);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="head.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="device_front_shared.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function*() {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
|
||||
if (SpecialPowers.isMainProcess()) {
|
||||
Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
|
||||
}
|
||||
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let win = yield openWebIDE();
|
||||
|
||||
let settingIframe = win.document.querySelector("#deck-panel-devicesettings");
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
|
||||
win.AppManager.update("runtime-list");
|
||||
|
||||
yield connectToLocalRuntime(win);
|
||||
|
||||
let settings = docRuntime.querySelector("#runtime-settings");
|
||||
|
||||
ok(!settings.hasAttribute("disabled"), "device settings cmd enabled");
|
||||
|
||||
let deck = win.document.querySelector("#deck");
|
||||
|
||||
win.Cmds.showSettings();
|
||||
is(deck.selectedPanel, settingIframe, "device settings iframe selected");
|
||||
|
||||
yield nextTick();
|
||||
|
||||
yield lazyIframeIsLoaded(settingIframe);
|
||||
|
||||
yield settingIframe.contentWindow.getAllSettings;
|
||||
|
||||
setDocument(settingIframe);
|
||||
|
||||
let fields = doc.querySelectorAll(".editable");
|
||||
|
||||
addNewField();
|
||||
|
||||
addNewFieldWithEnter();
|
||||
|
||||
editExistingField();
|
||||
|
||||
addNewFieldInteger();
|
||||
|
||||
yield editFieldInteger();
|
||||
|
||||
yield resetNewField("new-string-field");
|
||||
|
||||
addNewFieldBoolean();
|
||||
|
||||
searchFields(deck, "new-boolean-field2");
|
||||
|
||||
DebuggerServer.destroy();
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
SimpleTest.finish();
|
||||
}).then(null, e => {
|
||||
ok(false, "Exception: " + e);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -20,7 +20,7 @@
|
||||
Task.spawn(function*() {
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
let win = yield openWebIDE();
|
||||
let winIframe = win.document.querySelector("#project-listing-panel-details").contentWindow;
|
||||
let docProject = getProjectDocument(win);
|
||||
let packagedAppLocation = getTestFilePath("../app");
|
||||
let hostedAppManifest = TEST_BASE + "hosted_app.manifest";
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
is(project.location, hostedAppManifest, "Correctly reselected existing hosted app.");
|
||||
yield nextTick();
|
||||
|
||||
let panelNode = winIframe.document.querySelector("#project-panel");
|
||||
let panelNode = docProject.querySelector("#project-panel");
|
||||
let items = panelNode.querySelectorAll(".panel-item");
|
||||
// 3 controls, + 2 projects
|
||||
is(items.length, 5, "5 projects in panel");
|
||||
|
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
function connectToLocal(win, docRuntime) {
|
||||
let deferred = promise.defer();
|
||||
win.AppManager.connection.once(
|
||||
win.Connection.Events.CONNECTED,
|
||||
() => deferred.resolve());
|
||||
docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
let win = yield openWebIDE();
|
||||
let docProject = getProjectDocument(win);
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
win.AppManager.update("runtime-list");
|
||||
|
||||
yield connectToLocal(win, docRuntime);
|
||||
|
||||
// Select main process
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
SimpleTest.executeSoon(() => {
|
||||
docProject.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click();
|
||||
});
|
||||
|
||||
yield waitForUpdate(win, "project");
|
||||
|
||||
ok(win.UI.toolboxPromise, "Toolbox promise exists");
|
||||
yield win.UI.toolboxPromise;
|
||||
|
||||
ok(win.UI.toolboxIframe, "Toolbox iframe exists");
|
||||
|
||||
let nbox = win.document.querySelector("#notificationbox");
|
||||
ok(nbox.hasAttribute("toolboxfullscreen"), "Toolbox is fullsreen");
|
||||
|
||||
win.Cmds.showRuntimeDetails();
|
||||
|
||||
ok(!nbox.hasAttribute("toolboxfullscreen"), "Toolbox is not fullscreen");
|
||||
|
||||
yield win.Cmds.disconnectRuntime();
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
const { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
DebuggerServer.destroy();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -20,7 +20,7 @@
|
||||
Task.spawn(function*() {
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
let win = yield openWebIDE();
|
||||
let winIframe = win.document.querySelector("#project-listing-panel-details").contentWindow;
|
||||
let docProject = getProjectDocument(win);
|
||||
let packagedAppLocation = getTestFilePath("../app");
|
||||
|
||||
yield win.AppProjects.load();
|
||||
@ -57,7 +57,7 @@
|
||||
project = win.AppManager.selectedProject;
|
||||
ok(project.location.endsWith('manifest.webapp'), "The manifest was found and the project was updated");
|
||||
|
||||
let panelNode = winIframe.document.querySelector("#project-panel");
|
||||
let panelNode = docProject.querySelector("#project-panel");
|
||||
let items = panelNode.querySelectorAll(".panel-item");
|
||||
// 3 controls, + 2 projects
|
||||
is(items.length, 6, "6 projects in panel");
|
||||
|
@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let {TextDecoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
|
||||
|
||||
Task.spawn(function* () {
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
let win = yield openWebIDE();
|
||||
let AppManager = win.AppManager;
|
||||
|
||||
function isProjectMarkedAsValid() {
|
||||
let details = win.frames[1];
|
||||
return !details.document.body.classList.contains("error");
|
||||
}
|
||||
|
||||
let packagedAppLocation = getTestFilePath("../app");
|
||||
|
||||
yield win.projectList.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "details");
|
||||
|
||||
let project = win.AppManager.selectedProject;
|
||||
|
||||
ok("name" in project.manifest, "manifest includes name");
|
||||
is(project.name, project.manifest.name, "Display name uses manifest name");
|
||||
ok(isProjectMarkedAsValid(), "project is marked as valid");
|
||||
|
||||
// Change the name
|
||||
let originalName = project.manifest.name;
|
||||
|
||||
project.manifest.name = "xxx";
|
||||
|
||||
// Write to disk
|
||||
yield AppManager.writeManifest(project);
|
||||
|
||||
// Read file
|
||||
let manifestPath = OS.Path.join(packagedAppLocation, "manifest.webapp");
|
||||
let Decoder = new TextDecoder();
|
||||
let data = yield OS.File.read(manifestPath);
|
||||
data = new TextDecoder().decode(data);
|
||||
let json = JSON.parse(data);
|
||||
is(json.name, "xxx", "manifest written on disc");
|
||||
|
||||
// Make the manifest invalid on disk
|
||||
delete json.name;
|
||||
let Encoder = new TextEncoder();
|
||||
data = Encoder.encode(JSON.stringify(json));
|
||||
yield OS.File.writeAtomic(manifestPath, data , {tmpPath: manifestPath + ".tmp"});
|
||||
|
||||
// Trigger validation
|
||||
yield AppManager.validateAndUpdateProject(AppManager.selectedProject);
|
||||
yield nextTick();
|
||||
|
||||
ok(!("name" in project.manifest), "manifest has been updated");
|
||||
is(project.name, "--", "Placeholder is used for display name");
|
||||
ok(!isProjectMarkedAsValid(), "project is marked as invalid");
|
||||
|
||||
// Make the manifest valid on disk
|
||||
project.manifest.name = originalName;
|
||||
yield AppManager.writeManifest(project);
|
||||
|
||||
// Trigger validation
|
||||
yield AppManager.validateAndUpdateProject(AppManager.selectedProject);
|
||||
yield nextTick();
|
||||
|
||||
ok("name" in project.manifest, "manifest includes name");
|
||||
is(project.name, originalName, "Display name uses original manifest name");
|
||||
ok(isProjectMarkedAsValid(), "project is marked as valid");
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
yield removeAllProjects();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
46
browser/devtools/webide/test/sidebars/test_newapp.html
Normal file
46
browser/devtools/webide/test/sidebars/test_newapp.html
Normal file
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
let win = yield openWebIDE();
|
||||
let tmpDir = FileUtils.getDir("TmpD", []);
|
||||
yield win.projectList.newApp({
|
||||
index: 0,
|
||||
name: "webideTmpApp",
|
||||
folder: tmpDir
|
||||
});
|
||||
|
||||
let project = win.AppManager.selectedProject;
|
||||
tmpDir = FileUtils.getDir("TmpD", ["webidetmpapp"]);
|
||||
ok(tmpDir.isDirectory(), "Directory created");
|
||||
is(project.location, tmpDir.path, "Location is valid (and lowercase)");
|
||||
is(project.name, "webideTmpApp", "name field has been updated");
|
||||
|
||||
// Clean up
|
||||
tmpDir.remove(true);
|
||||
yield closeWebIDE(win);
|
||||
yield removeAllProjects();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -49,7 +49,8 @@
|
||||
}
|
||||
|
||||
win = yield openWebIDE();
|
||||
let winIframe = win.document.querySelector("#project-listing-panel-details").contentWindow;
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
let docProject = getProjectDocument(win);
|
||||
|
||||
win.AppManager.runtimeList.usb.push({
|
||||
connect: function(connection) {
|
||||
@ -64,15 +65,40 @@
|
||||
}
|
||||
});
|
||||
|
||||
win.AppManager.runtimeList.usb.push({
|
||||
connect: function(connection) {
|
||||
let deferred = promise.defer();
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
get name() {
|
||||
return "infiniteRuntime";
|
||||
}
|
||||
});
|
||||
|
||||
win.AppManager.runtimeList.usb.push({
|
||||
connect: function(connection) {
|
||||
let deferred = promise.defer();
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
prolongedConnection: true,
|
||||
|
||||
get name() {
|
||||
return "prolongedRuntime";
|
||||
}
|
||||
});
|
||||
|
||||
win.AppManager.update("runtime-list");
|
||||
|
||||
let packagedAppLocation = getTestFilePath("app");
|
||||
let packagedAppLocation = getTestFilePath("../app");
|
||||
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield win.projectList.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
|
||||
let panelNode = winIframe.document.querySelector("#runtime-panel");
|
||||
let panelNode = docRuntime.querySelector("#runtime-panel");
|
||||
let items = panelNode.querySelectorAll(".runtime-panel-item-usb");
|
||||
is(items.length, 1, "Found one runtime button");
|
||||
is(items.length, 3, "Found 3 runtime buttons");
|
||||
|
||||
let deferred = promise.defer();
|
||||
win.AppManager.connection.once(
|
||||
@ -86,7 +112,7 @@
|
||||
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
|
||||
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
yield waitForUpdate(win, "runtime-global-actors");
|
||||
|
||||
ok(isPlayActive(), "play button is enabled 1");
|
||||
ok(!isStopActive(), "stop button is disabled 1");
|
||||
@ -113,7 +139,7 @@
|
||||
ok(!isPlayActive(), "play button is disabled 4");
|
||||
ok(!isStopActive(), "stop button is disabled 4");
|
||||
|
||||
winIframe.document.querySelectorAll(".runtime-panel-item-other")[1].click();
|
||||
docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
|
||||
|
||||
yield waitForUpdate(win, "runtime-targets");
|
||||
|
||||
@ -122,9 +148,8 @@
|
||||
ok(win.AppManager.isMainProcessDebuggable(), "Main process available");
|
||||
|
||||
// Select main process
|
||||
yield win.Cmds.showProjectPanel();
|
||||
SimpleTest.executeSoon(() => {
|
||||
winIframe.document.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click();
|
||||
docProject.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click();
|
||||
});
|
||||
|
||||
yield waitForUpdate(win, "project");
|
||||
@ -137,6 +162,39 @@
|
||||
|
||||
yield win.Cmds.disconnectRuntime();
|
||||
|
||||
Services.prefs.setIntPref("devtools.webide.busyTimeout", 100);
|
||||
|
||||
// Wait for error message since connection never completes
|
||||
let errorDeferred = promise.defer();
|
||||
win.UI.reportError = errorName => {
|
||||
if (errorName === "error_operationTimeout") {
|
||||
errorDeferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
// Click the infinite runtime
|
||||
items[1].click();
|
||||
ok(win.document.querySelector("window").className, "busy", "UI is busy");
|
||||
yield errorDeferred.promise;
|
||||
|
||||
// Check for unexpected error message since this is prolonged
|
||||
let noErrorDeferred = promise.defer();
|
||||
win.UI.reportError = errorName => {
|
||||
if (errorName === "error_operationTimeout") {
|
||||
noErrorDeferred.reject();
|
||||
}
|
||||
};
|
||||
|
||||
// Click the prolonged runtime
|
||||
items[2].click();
|
||||
ok(win.document.querySelector("window").className, "busy", "UI is busy");
|
||||
|
||||
setTimeout(() => {
|
||||
noErrorDeferred.resolve();
|
||||
}, 1000);
|
||||
|
||||
yield noErrorDeferred.promise;
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
268
browser/devtools/webide/test/sidebars/test_telemetry.html
Normal file
268
browser/devtools/webide/test/sidebars/test_telemetry.html
Normal file
@ -0,0 +1,268 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="../head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
const Telemetry = require("devtools/shared/telemetry");
|
||||
const { _DeprecatedUSBRuntime, _WiFiRuntime, _SimulatorRuntime,
|
||||
_gRemoteRuntime, _gLocalRuntime, RuntimeTypes }
|
||||
= require("devtools/webide/runtimes");
|
||||
|
||||
// Because we need to gather stats for the period of time that a tool has
|
||||
// been opened we make use of setTimeout() to create tool active times.
|
||||
const TOOL_DELAY = 200;
|
||||
|
||||
function patchTelemetry() {
|
||||
Telemetry.prototype.telemetryInfo = {};
|
||||
Telemetry.prototype._oldlog = Telemetry.prototype.log;
|
||||
Telemetry.prototype.log = function(histogramId, value) {
|
||||
if (histogramId) {
|
||||
if (!this.telemetryInfo[histogramId]) {
|
||||
this.telemetryInfo[histogramId] = [];
|
||||
}
|
||||
this.telemetryInfo[histogramId].push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetTelemetry() {
|
||||
Telemetry.prototype.log = Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype.telemetryInfo;
|
||||
}
|
||||
|
||||
function cycleWebIDE() {
|
||||
return Task.spawn(function*() {
|
||||
let win = yield openWebIDE();
|
||||
// Wait a bit, so we're open for a non-zero time
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield closeWebIDE(win);
|
||||
});
|
||||
}
|
||||
|
||||
function addFakeRuntimes(win) {
|
||||
// We use the real runtimes here (and switch out some functionality)
|
||||
// so we can ensure that logging happens as it would in real use.
|
||||
|
||||
let usb = new _DeprecatedUSBRuntime("fakeUSB");
|
||||
// Use local pipe instead
|
||||
usb.connect = function(connection) {
|
||||
ok(connection, win.AppManager.connection, "connection is valid");
|
||||
connection.host = null; // force connectPipe
|
||||
connection.connect();
|
||||
return promise.resolve();
|
||||
};
|
||||
win.AppManager.runtimeList.usb.push(usb);
|
||||
|
||||
let wifi = new _WiFiRuntime("fakeWiFi");
|
||||
// Use local pipe instead
|
||||
wifi.connect = function(connection) {
|
||||
ok(connection, win.AppManager.connection, "connection is valid");
|
||||
connection.host = null; // force connectPipe
|
||||
connection.connect();
|
||||
return promise.resolve();
|
||||
};
|
||||
win.AppManager.runtimeList.wifi.push(wifi);
|
||||
|
||||
let sim = new _SimulatorRuntime("fakeSimulator");
|
||||
// Use local pipe instead
|
||||
sim.connect = function(connection) {
|
||||
ok(connection, win.AppManager.connection, "connection is valid");
|
||||
connection.host = null; // force connectPipe
|
||||
connection.connect();
|
||||
return promise.resolve();
|
||||
};
|
||||
Object.defineProperty(sim, "name", {
|
||||
get() {
|
||||
return this.version;
|
||||
}
|
||||
});
|
||||
win.AppManager.runtimeList.simulator.push(sim);
|
||||
|
||||
let remote = _gRemoteRuntime;
|
||||
// Use local pipe instead
|
||||
remote.connect = function(connection) {
|
||||
ok(connection, win.AppManager.connection, "connection is valid");
|
||||
connection.host = null; // force connectPipe
|
||||
connection.connect();
|
||||
return promise.resolve();
|
||||
};
|
||||
let local = _gLocalRuntime;
|
||||
|
||||
let other = Object.create(_gLocalRuntime);
|
||||
other.type = RuntimeTypes.OTHER;
|
||||
|
||||
win.AppManager.runtimeList.other = [remote, local, other];
|
||||
|
||||
win.AppManager.update("runtime-list");
|
||||
}
|
||||
|
||||
function addTestApp(win) {
|
||||
return Task.spawn(function*() {
|
||||
let packagedAppLocation = getTestFilePath("../app");
|
||||
yield win.projectList.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
});
|
||||
}
|
||||
|
||||
function startConnection(win, docRuntime, type, index) {
|
||||
let panelNode = docRuntime.querySelector("#runtime-panel");
|
||||
let items = panelNode.querySelectorAll(".runtime-panel-item-" + type);
|
||||
if (index === undefined) {
|
||||
is(items.length, 1, "Found one runtime button");
|
||||
}
|
||||
|
||||
let deferred = promise.defer();
|
||||
win.AppManager.connection.once(
|
||||
win.Connection.Events.CONNECTED,
|
||||
() => deferred.resolve());
|
||||
|
||||
items[index || 0].click();
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function waitUntilConnected(win) {
|
||||
return Task.spawn(function*() {
|
||||
ok(win.document.querySelector("window").className, "busy", "UI is busy");
|
||||
yield win.UI._busyPromise;
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
|
||||
});
|
||||
}
|
||||
|
||||
function connectToRuntime(win, docRuntime, type, index) {
|
||||
return Task.spawn(function*() {
|
||||
startConnection(win, docRuntime, type, index);
|
||||
yield waitUntilConnected(win);
|
||||
});
|
||||
}
|
||||
|
||||
function checkResults() {
|
||||
let result = Telemetry.prototype.telemetryInfo;
|
||||
for (let [histId, value] of Iterator(result)) {
|
||||
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
|
||||
ok(value.length === 1 && !!value[0],
|
||||
"Per user value " + histId + " has a single value of true");
|
||||
} else if (histId.endsWith("OPENED_BOOLEAN")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return !!element;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries are true");
|
||||
} else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element > 0;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries have time > 0");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTION_RESULT") {
|
||||
ok(value.length === 6, histId + " has 6 connection results");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return !!element;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " connections succeeded");
|
||||
} else if (histId.endsWith("CONNECTION_RESULT")) {
|
||||
ok(value.length === 1 && !!value[0],
|
||||
histId + " has 1 successful connection");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS") {
|
||||
ok(value.length === 6, histId + " has 6 connection results");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element > 0;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " connections have time > 0");
|
||||
} else if (histId.endsWith("USED")) {
|
||||
ok(value.length === 6, histId + " has 6 connection actions");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return !element;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " actions were skipped");
|
||||
} else {
|
||||
ok(false, "Unexpected " + histId + " was logged");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let win;
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
Task.spawn(function*() {
|
||||
if (win) {
|
||||
yield closeWebIDE(win);
|
||||
}
|
||||
DebuggerServer.destroy();
|
||||
yield removeAllProjects();
|
||||
resetTelemetry();
|
||||
});
|
||||
});
|
||||
|
||||
Task.spawn(function*() {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
Services.prefs.setBoolPref("devtools.webide.sidebars", true);
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
patchTelemetry();
|
||||
|
||||
// Cycle once, so we can test for multiple opens
|
||||
yield cycleWebIDE();
|
||||
|
||||
win = yield openWebIDE();
|
||||
let docRuntime = getRuntimeDocument(win);
|
||||
|
||||
// Wait a bit, so we're open for a non-zero time
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
addFakeRuntimes(win);
|
||||
yield addTestApp(win);
|
||||
|
||||
// Each one should log a connection result and non-zero connection
|
||||
// time
|
||||
yield connectToRuntime(win, docRuntime, "usb");
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "wifi");
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "simulator");
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "other", 0 /* remote */);
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "other", 1 /* local */);
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "other", 2 /* other */);
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield closeWebIDE(win);
|
||||
|
||||
checkResults();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -21,8 +21,11 @@
|
||||
Task.spawn(function* () {
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let win = yield openWebIDE();
|
||||
|
||||
|
@ -21,8 +21,11 @@
|
||||
Task.spawn(function* () {
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let win = yield openWebIDE();
|
||||
|
||||
|
@ -90,7 +90,7 @@
|
||||
|
||||
let packagedAppLocation = getTestFilePath("app");
|
||||
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield win.projectList.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
|
||||
let panelNode = win.document.querySelector("#runtime-panel");
|
||||
|
@ -17,5 +17,5 @@ webide.jar:
|
||||
skin/config-view.css (config-view.css)
|
||||
skin/wifi-auth.css (wifi-auth.css)
|
||||
skin/logs.css (logs.css)
|
||||
skin/project-listing.css (project-listing.css)
|
||||
skin/panel-listing.css (panel-listing.css)
|
||||
skin/simulator.css (simulator.css)
|
||||
|
@ -8,10 +8,10 @@ html {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.panel-item, label, #project-panel-projects {
|
||||
.panel-item, label, #project-panel-projects, #runtime-panel-projects {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.project-image, .panel-item span {
|
||||
@ -25,13 +25,14 @@ html {
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
color: #888;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
padding: 15px 0 5px;
|
||||
text-shadow: 1px 1px #fff;
|
||||
.panel-header {
|
||||
color: #ACACAC;
|
||||
text-transform: uppercase;
|
||||
line-height: 200%;
|
||||
margin: 5px 0;
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.panel-item {
|
||||
@ -39,3 +40,7 @@ label {
|
||||
padding: 5px 0;
|
||||
min-width: 130px;
|
||||
}
|
||||
|
||||
.panel-header[hidden] {
|
||||
display: none;
|
||||
}
|
@ -151,14 +151,14 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
#project-listing-panel {
|
||||
.panel-list {
|
||||
display: none;
|
||||
position: relative;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#project-listing-wrapper {
|
||||
.panel-list-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 100px;
|
||||
@ -169,7 +169,7 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#project-listing-panel-details {
|
||||
.panel-list-wrapper > iframe {
|
||||
height: inherit;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
@ -180,12 +180,14 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
}
|
||||
|
||||
/* TODO: remove once Bug 1079347 is complete */
|
||||
.project-listing, #project-listing-splitter {
|
||||
.project-listing, #project-listing-splitter, #runtime-listing-splitter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#project-listing-splitter[sidebar-displayed], #project-listing-panel[sidebar-displayed],
|
||||
#project-listing-panel[sidebar-displayed] .project-listing {
|
||||
#project-listing-panel[sidebar-displayed] .project-listing,
|
||||
#runtime-listing-splitter[sidebar-displayed], #runtime-listing-panel[sidebar-displayed],
|
||||
#runtime-listing-panel[sidebar-displayed] .runtime-listing {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@ -272,8 +274,9 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
#runtime-details { -moz-image-region: rect(156px,438px,182px,412px) }
|
||||
#runtime-screenshot { -moz-image-region: rect(130px,438px,156px,412px) }
|
||||
#runtime-permissions { -moz-image-region: rect(104px,438px,130px,412px) }
|
||||
#runtime-preferences { -moz-image-region: rect(104px,462px,129px,438px) }
|
||||
#runtime-settings { -moz-image-region: rect(104px,462px,129px,438px) }
|
||||
#runtime-preferences, #runtime-settings {
|
||||
-moz-image-region: rect(104px,462px,129px,438px);
|
||||
}
|
||||
#runtime-disconnect { -moz-image-region: rect(52px,438px,78px,412px) }
|
||||
#runtime-panel-nousbdevice { -moz-image-region: rect(156px,438px,182px,412px) }
|
||||
#runtime-panel-noadbhelper { -moz-image-region: rect(234px,438px,260px,412px) }
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user