Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-04-28 14:05:17 +02:00
commit c0f0b148a5
415 changed files with 12974 additions and 8106 deletions

27
.ycm_extra_conf.py Normal file
View 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
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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
*/

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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();
}

View File

@ -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

View File

@ -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");

View File

@ -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");

View File

@ -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;
}

View File

@ -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"/>

View File

@ -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";
}

View File

@ -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"},

View File

@ -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">

View File

@ -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,

View 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));
}
});
}
};

View File

@ -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']

View File

@ -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.

View File

@ -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) {

View File

@ -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

View File

@ -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,

View File

@ -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>

View File

@ -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)

View File

@ -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")

View File

@ -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")

View File

@ -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"))

View File

@ -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.
}

View File

@ -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;
}
};

View 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);

View 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);

View 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;
};

View 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);

File diff suppressed because it is too large Load Diff

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View File

@ -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)

View File

@ -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);
},

View File

@ -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);

View File

@ -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]

View 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();
});

View File

@ -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.");
}

View File

@ -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>

View File

@ -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 + "!");

View File

@ -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"

View File

@ -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;
},

View File

@ -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");

View File

@ -102,7 +102,8 @@ const CM_MAPPING = [
"clearHistory",
"openDialog",
"refresh",
"getScrollInfo"
"getScrollInfo",
"getViewport"
];
const { cssProperties, cssValues, cssColors } = getCSSKeywords();

View File

@ -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();
},

View File

@ -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]

View File

@ -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");
});

View File

@ -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}.`);
});

View 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;
}

View File

@ -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]

View File

@ -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.");
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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)

View File

@ -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();
}

View File

@ -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>

View File

@ -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;
}

View 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();
}

View 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>

View 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;
}
};

View File

@ -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() {

View File

@ -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>

View File

@ -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;

View File

@ -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;
}
};

View 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;
}
};

View File

@ -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',

View File

@ -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");

View File

@ -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");

View File

@ -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

View 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;
});
}

View File

@ -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]

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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");

View File

@ -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>

View File

@ -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");

View File

@ -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>

View 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>

View File

@ -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();
});
}

View 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>

View File

@ -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();

View File

@ -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();

View File

@ -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");

View File

@ -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)

View File

@ -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;
}

View File

@ -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