Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-03-22 12:31:09 +01:00
commit 53f935b036
278 changed files with 3324 additions and 16622 deletions

View File

@ -123,15 +123,7 @@ SettingsListener.observe('language.current', 'en-US', function(value) {
Services.prefs.setCharPref(prefName, value);
if (shell.hasStarted() == false) {
// On b2gdroid at first run we need to synchronize our wallpaper with
// Android one's before bootstrapping.
if (AppConstants.MOZ_B2GDROID) {
Cc["@mozilla.org/b2g/b2gdroid-setup;1"]
.getService().wrappedJSObject.setWallpaper()
.then(() => { shell.bootstrap(); });
} else {
shell.bootstrap();
}
shell.bootstrap();
}
});
@ -359,8 +351,7 @@ setUpdateTrackingId();
});
}
syncPrefDefault(AppConstants.MOZ_B2GDROID ? 'app.update.url.android'
: 'app.update.url');
syncPrefDefault('app.update.url');
syncPrefDefault('app.update.channel');
})();

View File

@ -262,11 +262,6 @@ var shell = {
},
bootstrap: function() {
if (AppConstants.MOZ_B2GDROID) {
Cc["@mozilla.org/b2g/b2gdroid-setup;1"]
.getService(Ci.nsIObserver).observe(window, "shell-startup", null);
}
window.performance.mark('gecko-shell-bootstrap');
// Before anything, check if we want to start in safe mode.
@ -739,7 +734,7 @@ var shell = {
handleCmdLine: function() {
// This isn't supported on devices.
if (!isGonk && !AppConstants.MOZ_B2GDROID) {
if (!isGonk) {
let b2gcmds = Cc["@mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds"]
.getService(Ci.nsISupports);
let args = b2gcmds.wrappedJSObject.cmdLine;

View File

@ -16,15 +16,12 @@ contract @mozilla.org/updates/update-prompt;1 {88b3eb21-d072-4e3b-886d-f89d8c49f
category system-update-provider MozillaProvider @mozilla.org/updates/update-prompt;1,{88b3eb21-d072-4e3b-886d-f89d8c49fe59}
#endif
# On b2gdroid we want to use the android implementation of the directory service.
#ifndef MOZ_B2GDROID
#ifdef MOZ_B2G
# DirectoryProvider.js
component {9181eb7c-6f87-11e1-90b1-4f59d80dd2e5} DirectoryProvider.js
contract @mozilla.org/b2g/directory-provider;1 {9181eb7c-6f87-11e1-90b1-4f59d80dd2e5}
category xpcom-directory-providers b2g-directory-provider @mozilla.org/b2g/directory-provider;1
#endif
#endif
# ActivitiesGlue.js
component {3a54788b-48cc-4ab4-93d6-0d6a8ef74f8e} ActivitiesGlue.js
@ -94,7 +91,6 @@ contract @mozilla.org/fxaccounts/fxaccounts-ui-glue;1 {51875c14-91d7-4b8c-b65d-3
component {710322af-e6ae-4b0c-b2c9-1474a87b077e} HelperAppDialog.js
contract @mozilla.org/helperapplauncherdialog;1 {710322af-e6ae-4b0c-b2c9-1474a87b077e}
#ifndef MOZ_B2GDROID
#ifndef MOZ_WIDGET_GONK
component {c83c02c0-5d43-4e3e-987f-9173b313e880} SimulatorScreen.js
contract @mozilla.org/simulator-screen;1 {c83c02c0-5d43-4e3e-987f-9173b313e880}
@ -108,7 +104,6 @@ component {385993fe-8710-4621-9fb1-00a09d8bec37} CommandLine.js
contract @mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds {385993fe-8710-4621-9fb1-00a09d8bec37}
category command-line-handler m-b2gcmds @mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds
#endif
#endif
# BootstrapCommandLine.js
component {fd663ec8-cf3f-4c2b-aacb-17a6915ccb44} BootstrapCommandLine.js

View File

@ -43,7 +43,7 @@ EXTRA_PP_COMPONENTS += [
'B2GComponents.manifest',
]
if CONFIG['MOZ_B2G'] and not CONFIG['MOZ_B2GDROID']:
if CONFIG['MOZ_B2G']:
EXTRA_COMPONENTS += [
'DirectoryProvider.js',
'RecoveryService.js',

View File

@ -2,15 +2,7 @@
# 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/.
# For b2gdroid, gaia ends up in the assets/gaia folder in the APK.
GAIA_PATH := $(if MOZ_B2GDROID,gaia/assets/gaia,gaia/profile)
# For b2gdroid, we disable the screen timeout since this is managed by android.
# We also limit the app set to the production ones.
GAIA_OPTIONS := $(if MOZ_B2GDROID, \
GAIA_DISTRIBUTION_DIR=distros/b2gdroid \
NO_LOCK_SCREEN=1 \
)
GAIA_PATH := gaia/profile
GENERATED_DIRS += $(DIST)/bin/$(GAIA_PATH)
@ -18,5 +10,5 @@ include $(topsrcdir)/config/rules.mk
libs::
+$(MAKE) -j1 -C $(GAIADIR) clean
+$(GAIA_OPTIONS) $(MAKE) -j1 -C $(GAIADIR) profile
+$(MAKE) -j1 -C $(GAIADIR) profile
(cd $(GAIADIR)/profile && tar $(TAR_CREATE_FLAGS) - .) | (cd $(ABS_DIST)/bin/$(GAIA_PATH) && tar -xf -)

View File

@ -4,19 +4,17 @@
# 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/.
if not CONFIG['MOZ_B2GDROID']:
# b2gdroid does not build a runner executable, but it does build gaia; see Makefile.in.
Program(CONFIG['MOZ_APP_NAME'])
Program(CONFIG['MOZ_APP_NAME'])
if CONFIG['OS_ARCH'] == 'WINNT':
SOURCES += [
'run-b2g.cpp',
]
DEFINES['B2G_NAME'] = 'L"%s-bin%s"' % (PROGRAM, CONFIG['BIN_SUFFIX'])
DEFINES['GAIA_PATH'] = 'L"gaia\\\\profile"'
else:
SOURCES += [
'run-b2g.c',
]
DEFINES['B2G_NAME'] = '"%s-bin%s"' % (PROGRAM, CONFIG['BIN_SUFFIX'])
DEFINES['GAIA_PATH'] = '"gaia/profile"'
if CONFIG['OS_ARCH'] == 'WINNT':
SOURCES += [
'run-b2g.cpp',
]
DEFINES['B2G_NAME'] = 'L"%s-bin%s"' % (PROGRAM, CONFIG['BIN_SUFFIX'])
DEFINES['GAIA_PATH'] = 'L"gaia\\\\profile"'
else:
SOURCES += [
'run-b2g.c',
]
DEFINES['B2G_NAME'] = '"%s-bin%s"' % (PROGRAM, CONFIG['BIN_SUFFIX'])
DEFINES['GAIA_PATH'] = '"gaia/profile"'

View File

@ -1049,16 +1049,9 @@ var gBrowserInit = {
this._cancelDelayedStartup();
// We need to set the MozApplicationManifest event listeners up
// before we start loading the home pages in case a document has
// a "manifest" attribute, in which the MozApplicationManifest event
// will be fired.
gBrowser.addEventListener("MozApplicationManifest",
OfflineApps, false);
// listen for offline apps on social
let socialBrowser = document.getElementById("social-sidebar-browser");
socialBrowser.addEventListener("MozApplicationManifest",
OfflineApps, false);
// We need to set the OfflineApps message listeners up before we
// load homepages, which might need them.
OfflineApps.init();
// This pageshow listener needs to be registered before we may call
// swapBrowsersAndCloseOther() to receive pageshow events fired by that.
@ -1176,7 +1169,6 @@ var gBrowserInit = {
window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
BrowserOffline.init();
OfflineApps.init();
IndexedDBPromptHelper.init();
if (AppConstants.E10S_TESTING_ONLY)
@ -1498,7 +1490,6 @@ var gBrowserInit = {
}
BrowserOffline.uninit();
OfflineApps.uninit();
IndexedDBPromptHelper.uninit();
LightweightThemeListener.uninit();
PanelUI.uninit();
@ -5745,141 +5736,49 @@ var BrowserOffline = {
};
var OfflineApps = {
/////////////////////////////////////////////////////////////////////////////
// OfflineApps Public Methods
init: function ()
{
Services.obs.addObserver(this, "offline-cache-update-completed", false);
},
uninit: function ()
{
Services.obs.removeObserver(this, "offline-cache-update-completed");
},
handleEvent: function(event) {
if (event.type == "MozApplicationManifest") {
this.offlineAppRequested(event.originalTarget.defaultView);
}
},
/////////////////////////////////////////////////////////////////////////////
// OfflineApps Implementation Methods
// XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow
// were taken from browser/components/feeds/WebContentConverter.
_getBrowserWindowForContentWindow: function(aContentWindow) {
return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.wrappedJSObject;
},
_getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) {
// This depends on pseudo APIs of browser.js and tabbrowser.xml
aContentWindow = aContentWindow.top;
var browsers = aBrowserWindow.gBrowser.browsers;
for (let browser of browsers) {
if (browser.contentWindow == aContentWindow)
return browser;
}
// handle other browser/iframe elements that may need popupnotifications
let browser = aContentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
if (browser.getAttribute("popupnotificationanchor"))
return browser;
return null;
},
_getManifestURI: function(aWindow) {
if (!aWindow.document.documentElement)
return null;
var attr = aWindow.document.documentElement.getAttribute("manifest");
if (!attr)
return null;
try {
var contentURI = makeURI(aWindow.location.href, null, null);
return makeURI(attr, aWindow.document.characterSet, contentURI);
} catch (e) {
return null;
}
},
// A cache update isn't tied to a specific window. Try to find
// the best browser in which to warn the user about space usage
_getBrowserForCacheUpdate: function(aCacheUpdate) {
// Prefer the current browser
var uri = this._getManifestURI(content);
if (uri && uri.equals(aCacheUpdate.manifestURI)) {
return gBrowser.selectedBrowser;
}
var browsers = gBrowser.browsers;
for (let browser of browsers) {
uri = this._getManifestURI(browser.contentWindow);
if (uri && uri.equals(aCacheUpdate.manifestURI)) {
return browser;
}
}
// is this from a non-tab browser/iframe?
browsers = document.querySelectorAll("iframe[popupnotificationanchor] | browser[popupnotificationanchor]");
for (let browser of browsers) {
uri = this._getManifestURI(browser.contentWindow);
if (uri && uri.equals(aCacheUpdate.manifestURI)) {
return browser;
}
}
return null;
},
_warnUsage: function(aBrowser, aURI) {
if (!aBrowser)
warnUsage(browser, uri) {
if (!browser)
return;
let mainAction = {
label: gNavigatorBundle.getString("offlineApps.manageUsage"),
accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"),
callback: OfflineApps.manage
callback: this.manage
};
let warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
// This message shows the quota in MB, and so we divide the quota (in kb) by 1024.
let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
[ aURI.host,
warnQuota / 1024 ]);
[ uri.host,
warnQuotaKB / 1024 ]);
let anchorID = "indexedDB-notification-icon";
PopupNotifications.show(aBrowser, "offline-app-usage", message,
PopupNotifications.show(browser, "offline-app-usage", message,
anchorID, mainAction);
// Now that we've warned once, prevent the warning from showing up
// again.
Services.perms.add(aURI, "offline-app",
Services.perms.add(uri, "offline-app",
Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
},
// XXX: duplicated in preferences/advanced.js
_getOfflineAppUsage: function (host, groups)
{
var cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
_getOfflineAppUsage(host, groups) {
let cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
getService(Ci.nsIApplicationCacheService);
if (!groups)
groups = cacheService.getGroups();
if (!groups) {
try {
groups = cacheService.getGroups();
} catch (ex) {
return 0;
}
}
var usage = 0;
let usage = 0;
for (let group of groups) {
var uri = Services.io.newURI(group, null, null);
let uri = Services.io.newURI(group, null, null);
if (uri.asciiHost == host) {
var cache = cacheService.getActiveCache(group);
let cache = cacheService.getActiveCache(group);
usage += cache.usage;
}
}
@ -5887,13 +5786,15 @@ var OfflineApps = {
return usage;
},
_checkUsage: function(aURI) {
_usedMoreThanWarnQuota(uri) {
// if the user has already allowed excessive usage, don't bother checking
if (Services.perms.testExactPermission(aURI, "offline-app") !=
if (Services.perms.testExactPermission(uri, "offline-app") !=
Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) {
var usage = this._getOfflineAppUsage(aURI.asciiHost);
var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
if (usage >= warnQuota * 1024) {
let usageBytes = this._getOfflineAppUsage(uri.asciiHost);
let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
// The pref is in kb, the usage we get is in bytes, so multiply the quota
// to compare correctly:
if (usageBytes >= warnQuotaKB * 1024) {
return true;
}
}
@ -5901,43 +5802,22 @@ var OfflineApps = {
return false;
},
offlineAppRequested: function(aContentWindow) {
if (!gPrefService.getBoolPref("browser.offline-apps.notify")) {
return;
}
let browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
let browser = this._getBrowserForContentWindow(browserWindow,
aContentWindow);
let currentURI = aContentWindow.document.documentURIObject;
// don't bother showing UI if the user has already made a decision
if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
return;
try {
if (gPrefService.getBoolPref("offline-apps.allow_by_default")) {
// all pages can use offline capabilities, no need to ask the user
return;
}
} catch(e) {
// this pref isn't set by default, ignore failures
}
let host = currentURI.asciiHost;
requestPermission(browser, docId, uri) {
let host = uri.asciiHost;
let notificationID = "offline-app-requested-" + host;
let notification = PopupNotifications.getNotification(notificationID, browser);
if (notification) {
notification.options.documents.push(aContentWindow.document);
notification.options.controlledItems.push([
Cu.getWeakReference(browser), docId, uri
]);
} else {
let mainAction = {
label: gNavigatorBundle.getString("offlineApps.allow"),
accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
callback: function() {
for (let document of notification.options.documents) {
OfflineApps.allowSite(document);
for (let [browser, docId, uri] of notification.options.controlledItems) {
OfflineApps.allowSite(browser, docId, uri);
}
}
};
@ -5945,16 +5825,16 @@ var OfflineApps = {
label: gNavigatorBundle.getString("offlineApps.never"),
accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
callback: function() {
for (let document of notification.options.documents) {
OfflineApps.disallowSite(document);
for (let [, , uri] of notification.options.controlledItems) {
OfflineApps.disallowSite(uri);
}
}
}];
let message = gNavigatorBundle.getFormattedString("offlineApps.available",
[ host ]);
[host]);
let anchorID = "indexedDB-notification-icon";
let options= {
documents : [ aContentWindow.document ]
let options = {
controlledItems : [[Cu.getWeakReference(browser), docId, uri]]
};
notification = PopupNotifications.show(browser, notificationID, message,
anchorID, mainAction,
@ -5962,56 +5842,47 @@ var OfflineApps = {
}
},
allowSite: function(aDocument) {
Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION);
disallowSite(uri) {
Services.perms.add(uri, "offline-app", Services.perms.DENY_ACTION);
},
allowSite(browserRef, docId, uri) {
Services.perms.add(uri, "offline-app", Services.perms.ALLOW_ACTION);
// When a site is enabled while loading, manifest resources will
// start fetching immediately. This one time we need to do it
// ourselves.
this._startFetching(aDocument);
let browser = browserRef.get();
if (browser && browser.messageManager) {
browser.messageManager.sendAsyncMessage("OfflineApps:StartFetching", {
docId,
});
}
},
disallowSite: function(aDocument) {
Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION);
},
manage: function() {
manage() {
openAdvancedPreferences("networkTab");
},
_startFetching: function(aDocument) {
if (!aDocument.documentElement)
return;
var manifest = aDocument.documentElement.getAttribute("manifest");
if (!manifest)
return;
var manifestURI = makeURI(manifest, aDocument.characterSet,
aDocument.documentURIObject);
var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject,
aDocument.nodePrincipal, window);
receiveMessage(msg) {
switch (msg.name) {
case "OfflineApps:CheckUsage":
let uri = makeURI(msg.data.uri);
if (this._usedMoreThanWarnQuota(uri)) {
this.warnUsage(msg.target, uri);
}
break;
case "OfflineApps:RequestPermission":
this.requestPermission(msg.target, msg.data.docId, makeURI(msg.data.uri));
break;
}
},
/////////////////////////////////////////////////////////////////////////////
// nsIObserver
observe: function (aSubject, aTopic, aState)
{
if (aTopic == "offline-cache-update-completed") {
var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
var uri = cacheUpdate.manifestURI;
if (OfflineApps._checkUsage(uri)) {
var browser = this._getBrowserForCacheUpdate(cacheUpdate);
if (browser) {
OfflineApps._warnUsage(browser, cacheUpdate.manifestURI);
}
}
}
}
init() {
let mm = window.messageManager;
mm.addMessageListener("OfflineApps:CheckUsage", this);
mm.addMessageListener("OfflineApps:RequestPermission", this);
},
};
var IndexedDBPromptHelper = {

View File

@ -472,6 +472,33 @@
accesskey="&syncSyncNowItem.accesskey;"
id="syncedTabsRefresh"/>
</menupopup>
<menupopup id="SyncedTabsSidebarTabsFilterContext"
class="textbox-contextmenu">
<menuitem label="&undoCmd.label;"
accesskey="&undoCmd.accesskey;"
cmd="cmd_undo"/>
<menuseparator/>
<menuitem label="&cutCmd.label;"
accesskey="&cutCmd.accesskey;"
cmd="cmd_cut"/>
<menuitem label="&copyCmd.label;"
accesskey="&copyCmd.accesskey;"
cmd="cmd_copy"/>
<menuitem label="&pasteCmd.label;"
accesskey="&pasteCmd.accesskey;"
cmd="cmd_paste"/>
<menuitem label="&deleteCmd.label;"
accesskey="&deleteCmd.accesskey;"
cmd="cmd_delete"/>
<menuseparator/>
<menuitem label="&selectAllCmd.label;"
accesskey="&selectAllCmd.accesskey;"
cmd="cmd_selectAll"/>
<menuseparator/>
<menuitem label="&syncSyncNowItem.label;"
accesskey="&syncSyncNowItem.accesskey;"
id="syncedTabsRefreshFilter"/>
</menupopup>
</popupset>
#ifdef CAN_DRAW_IN_TITLEBAR

View File

@ -1318,3 +1318,110 @@ var PageInfoListener = {
}
};
PageInfoListener.init();
let OfflineApps = {
_docId: 0,
_docIdMap: new Map(),
_docManifestSet: new Set(),
_observerAdded: false,
registerWindow(aWindow) {
if (!this._observerAdded) {
this._observerAdded = true;
Services.obs.addObserver(this, "offline-cache-update-completed", true);
}
let manifestURI = this._getManifestURI(aWindow);
this._docManifestSet.add(manifestURI.spec);
},
handleEvent(event) {
if (event.type == "MozApplicationManifest") {
this.offlineAppRequested(event.originalTarget.defaultView);
}
},
_getManifestURI(aWindow) {
if (!aWindow.document.documentElement)
return null;
var attr = aWindow.document.documentElement.getAttribute("manifest");
if (!attr)
return null;
try {
var contentURI = BrowserUtils.makeURI(aWindow.location.href, null, null);
return BrowserUtils.makeURI(attr, aWindow.document.characterSet, contentURI);
} catch (e) {
return null;
}
},
offlineAppRequested(aContentWindow) {
this.registerWindow(aContentWindow);
if (!Services.prefs.getBoolPref("browser.offline-apps.notify")) {
return;
}
let currentURI = aContentWindow.document.documentURIObject;
// don't bother showing UI if the user has already made a decision
if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
return;
try {
if (Services.prefs.getBoolPref("offline-apps.allow_by_default")) {
// all pages can use offline capabilities, no need to ask the user
return;
}
} catch(e) {
// this pref isn't set by default, ignore failures
}
let docId = ++this._docId;
this._docIdMap.set(docId, Cu.getWeakReference(aContentWindow.document));
sendAsyncMessage("OfflineApps:RequestPermission", {
uri: currentURI.spec,
docId,
});
},
_startFetching(aDocument) {
if (!aDocument.documentElement)
return;
let manifestURI = this._getManifestURI(aDocument.defaultView);
if (!manifestURI)
return;
var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject,
aDocument.nodePrincipal, aDocument.defaultView);
},
receiveMessage(aMessage) {
if (aMessage.name == "OfflineApps:StartFetching") {
let doc = this._docIdMap.get(aMessage.data.docId);
doc = doc && doc.get();
if (doc) {
this._startFetching(doc);
}
this._docIdMap.delete(aMessage.data.docId);
}
},
observe(aSubject, aTopic, aState) {
if (aTopic == "offline-cache-update-completed") {
let cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
let uri = cacheUpdate.manifestURI;
if (uri && this._docManifestSet.has(uri.spec)) {
sendAsyncMessage("OfflineApps:CheckUsage", {uri: uri.spec});
}
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
};
addEventListener("MozApplicationManifest", OfflineApps, false);
addMessageListener("OfflineApps:StartFetching", OfflineApps);

View File

@ -350,7 +350,10 @@ skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (
[browser_mixedcontent_securityflags.js]
tags = mcb
[browser_offlineQuotaNotification.js]
skip-if = buildapp == 'mulet' || e10s # Bug 1093603 - test breaks with PopupNotifications.panel.firstElementChild is null
skip-if = buildapp == 'mulet'
[browser_gZipOfflineChild.js]
skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^
[browser_openPromptInBackgroundTab.js]
support-files = openPromptOffTimeout.html
[browser_overflowScroll.js]

View File

@ -0,0 +1,80 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/test_offline_gzip.html"
registerCleanupFunction(function() {
// Clean up after ourself
let uri = Services.io.newURI(URL, null, null);
let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
Services.perms.removeFromPrincipal(principal, "offline-app");
Services.prefs.clearUserPref("offline-apps.allow_by_default");
});
var cacheCount = 0;
var intervalID = 0;
////
// Handle "message" events which are posted from the iframe upon
// offline cache events.
//
function handleMessageEvents(event) {
cacheCount++;
switch (cacheCount) {
case 1:
// This is the initial caching off offline data.
is(event.data, "oncache", "Child was successfully cached.");
// Reload the frame; this will generate an error message
// in the case of bug 501422.
event.source.location.reload();
// Use setInterval to repeatedly call a function which
// checks that one of two things has occurred: either
// the offline cache is udpated (which means our iframe
// successfully reloaded), or the string "error" appears
// in the iframe, as in the case of bug 501422.
intervalID = setInterval(function() {
// Sometimes document.body may not exist, and trying to access
// it will throw an exception, so handle this case.
try {
var bodyInnerHTML = event.source.document.body.innerHTML;
}
catch (e) {
var bodyInnerHTML = "";
}
if (cacheCount == 2 || bodyInnerHTML.includes("error")) {
clearInterval(intervalID);
is(cacheCount, 2, "frame not reloaded successfully");
if (cacheCount != 2) {
finish();
}
}
}, 100);
break;
case 2:
is(event.data, "onupdate", "Child was successfully updated.");
clearInterval(intervalID);
finish();
break;
default:
// how'd we get here?
ok(false, "cacheCount not 1 or 2");
}
}
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref("offline-apps.allow_by_default", true);
// Open a new tab.
gBrowser.selectedTab = gBrowser.addTab(URL);
registerCleanupFunction(() => gBrowser.removeCurrentTab());
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
let window = gBrowser.selectedBrowser.contentWindow;
window.addEventListener("message", handleMessageEvents, false);
});
}

View File

@ -15,6 +15,8 @@ registerCleanupFunction(function() {
Services.perms.removeFromPrincipal(principal, "offline-app");
Services.prefs.clearUserPref("offline-apps.quota.warn");
Services.prefs.clearUserPref("offline-apps.allow_by_default");
let {OfflineAppCacheHelper} = Components.utils.import("resource:///modules/offlineAppCache.jsm", {});
OfflineAppCacheHelper.clear();
});
// Same as the other one, but for in-content preferences
@ -44,30 +46,42 @@ function test() {
// Wait for a notification that asks whether to allow offline storage.
promiseNotification(),
// Wait for the tab to load.
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser)
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser),
]).then(() => {
gBrowser.selectedBrowser.contentWindow.applicationCache.oncached = function() {
executeSoon(function() {
// We got cached - now we should have provoked the quota warning.
let notification = PopupNotifications.getNotification('offline-app-usage');
ok(notification, "have offline-app-usage notification");
// select the default action - this should cause the preferences
// tab to open - which we track via an "Initialized" event.
PopupNotifications.panel.firstElementChild.button.click();
let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
newTabBrowser.addEventListener("Initialized", function PrefInit() {
newTabBrowser.removeEventListener("Initialized", PrefInit, true);
executeSoon(function() {
checkInContentPreferences(newTabBrowser.contentWindow);
})
}, true);
info("Loaded page, adding onCached handler");
// Need a promise to keep track of when we've added our handler.
let mm = gBrowser.selectedBrowser.messageManager;
let onCachedAttached = BrowserTestUtils.waitForMessage(mm, "Test:OnCachedAttached");
let gotCached = ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
return new Promise(resolve => {
content.window.applicationCache.oncached = function() {
setTimeout(resolve, 0);
};
sendAsyncMessage("Test:OnCachedAttached");
});
};
Services.prefs.setIntPref("offline-apps.quota.warn", 1);
});
gotCached.then(function() {
// We got cached - now we should have provoked the quota warning.
let notification = PopupNotifications.getNotification('offline-app-usage');
ok(notification, "have offline-app-usage notification");
// select the default action - this should cause the preferences
// tab to open - which we track via an "Initialized" event.
PopupNotifications.panel.firstElementChild.button.click();
let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
newTabBrowser.addEventListener("Initialized", function PrefInit() {
newTabBrowser.removeEventListener("Initialized", PrefInit, true);
executeSoon(function() {
checkInContentPreferences(newTabBrowser.contentWindow);
})
}, true);
});
onCachedAttached.then(function() {
Services.prefs.setIntPref("offline-apps.quota.warn", 1);
// Click the notification panel's "Allow" button. This should kick
// off updates which will call our oncached handler above.
PopupNotifications.panel.firstElementChild.button.click();
// Click the notification panel's "Allow" button. This should kick
// off updates which will call our oncached handler above.
PopupNotifications.panel.firstElementChild.button.click();
});
});
}

View File

@ -27,6 +27,9 @@ const kWhitelist = [
// Highlighter CSS uses a UA-only pseudo-class, see bug 985597.
{sourceName: /highlighters\.css$/i,
errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i},
// Responsive Design Mode CSS uses a UA-only pseudo-class, see Bug 1241714.
{sourceName: /responsive-ua\.css$/i,
errorMessage: /Unknown pseudo-class.*moz-dropdown-list/i},
];
var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");

View File

@ -8,10 +8,6 @@ support-files =
contextmenu_common.js
ctxmenu-image.png
feed_discovery.html
gZipOfflineChild.cacheManifest
gZipOfflineChild.cacheManifest^headers^
gZipOfflineChild.html
gZipOfflineChild.html^headers^
head_plain.js
offlineByDefault.js
offlineChild.cacheManifest
@ -32,5 +28,3 @@ support-files =
skip-if = e10s
[test_offlineNotification.html]
skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
[test_offline_gzip.html]
skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?

View File

@ -9,107 +9,13 @@ cache, it can be fetched from the cache successfully.
-->
<head>
<title>Test gzipped offline resources</title>
<script type="text/javascript"
src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript"
src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="offlineByDefault.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<meta charset="utf-8">
</head>
<body onload="loaded()">
<body>
<p id="display">
<iframe name="testFrame" src="gZipOfflineChild.html"></iframe>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
var cacheCount = 0;
var intervalID = 0;
window.addEventListener("message", handleMessageEvents, false);
SimpleTest.waitForExplicitFinish();
function finishTest() {
// Clean up after ourselves.
var Cc = SpecialPowers.Cc;
var pm = Cc["@mozilla.org/permissionmanager;1"].
getService(SpecialPowers.Ci.nsIPermissionManager);
var uri = Cc["@mozilla.org/network/io-service;1"].getService(SpecialPowers.Ci.nsIIOService)
.newURI(window.frames[0].location, null, null);
var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(SpecialPowers.Ci.nsIScriptSecurityManager);
var principal = ssm.createCodebasePrincipal(uri, {});
pm.removeFromPrincipal(principal, "offline-app");
window.removeEventListener("message", handleMessageEvents, false);
offlineByDefault.reset();
SimpleTest.finish();
}
////
// Handle "message" events which are posted from the iframe upon
// offline cache events.
//
function handleMessageEvents(event) {
cacheCount++;
switch (cacheCount) {
case 1:
// This is the initial caching off offline data.
is(event.data, "oncache", "Child was successfully cached.");
// Reload the frame; this will generate an error message
// in the case of bug 501422.
frames.testFrame.window.location.reload();
// Use setInterval to repeatedly call a function which
// checks that one of two things has occurred: either
// the offline cache is udpated (which means our iframe
// successfully reloaded), or the string "error" appears
// in the iframe, as in the case of bug 501422.
intervalID = setInterval(function() {
// Sometimes document.body may not exist, and trying to access
// it will throw an exception, so handle this case.
try {
var bodyInnerHTML = frames.testFrame.document.body.innerHTML;
}
catch (e) {
var bodyInnerHTML = "";
}
if (cacheCount == 2 || bodyInnerHTML.includes("error")) {
clearInterval(intervalID);
is(cacheCount, 2, "frame not reloaded successfully");
if (cacheCount != 2) {
finishTest();
}
}
}, 100);
break;
case 2:
is(event.data, "onupdate", "Child was successfully updated.");
clearInterval(intervalID);
finishTest();
break;
default:
// how'd we get here?
ok(false, "cacheCount not 1 or 2");
}
}
function loaded() {
// Click the notification panel's "Allow" button. This should kick
// off updates, which will eventually lead to getting messages from
// the iframe.
var wm = SpecialPowers.Cc["@mozilla.org/appshell/window-mediator;1"].
getService(SpecialPowers.Ci.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
var panel = win.PopupNotifications.panel;
panel.firstElementChild.button.click();
}
</script>
</pre>
</body>
</html>

View File

@ -503,18 +503,22 @@ var gAdvancedPane = {
},
// XXX: duplicated in browser.js
_getOfflineAppUsage: function (perm, groups)
{
var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
getService(Components.interfaces.nsIApplicationCacheService);
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
_getOfflineAppUsage(perm, groups) {
let cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
getService(Ci.nsIApplicationCacheService);
if (!groups) {
try {
groups = cacheService.getGroups();
} catch (ex) {
return 0;
}
}
var usage = 0;
for (var i = 0; i < groups.length; i++) {
var uri = ios.newURI(groups[i], null, null);
let usage = 0;
for (let group of groups) {
let uri = Services.io.newURI(group, null, null);
if (perm.matchesURI(uri, true)) {
var cache = cacheService.getActiveCache(groups[i]);
let cache = cacheService.getActiveCache(group);
usage += cache.usage;
}
}

View File

@ -21,6 +21,10 @@ function getContextMenu(window) {
return getChromeWindow(window).document.getElementById("SyncedTabsSidebarContext");
}
function getTabsFilterContextMenu(window) {
return getChromeWindow(window).document.getElementById("SyncedTabsSidebarTabsFilterContext");
}
/*
* TabListView
*
@ -330,21 +334,71 @@ TabListView.prototype = {
// Set up the custom context menu
_setupContextMenu() {
this._handleContentContextMenu = event =>
this.handleContentContextMenu(event);
this._handleContentContextMenuCommand = event =>
this.handleContentContextMenuCommand(event);
Services.els.addSystemEventListener(this._window, "contextmenu", this._handleContentContextMenu, false);
let menu = getContextMenu(this._window);
menu.addEventListener("command", this._handleContentContextMenuCommand, true);
Services.els.addSystemEventListener(this._window, "contextmenu", this, false);
for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
let menu = getMenu(this._window);
menu.addEventListener("popupshowing", this, true);
menu.addEventListener("command", this, true);
}
},
_teardownContextMenu() {
// Tear down context menu
Services.els.removeSystemEventListener(this._window, "contextmenu", this._handleContentContextMenu, false);
let menu = getContextMenu(this._window);
menu.removeEventListener("command", this._handleContentContextMenuCommand, true);
Services.els.removeSystemEventListener(this._window, "contextmenu", this, false);
for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
let menu = getMenu(this._window);
menu.removeEventListener("popupshowing", this, true);
menu.removeEventListener("command", this, true);
}
},
handleEvent(event) {
switch (event.type) {
case "contextmenu":
this.handleContextMenu(event);
break;
case "popupshowing": {
if (event.target.getAttribute("id") == "SyncedTabsSidebarTabsFilterContext") {
this.handleTabsFilterContextMenuShown(event);
}
break;
}
case "command": {
let menu = event.target.closest("menupopup");
switch (menu.getAttribute("id")) {
case "SyncedTabsSidebarContext":
this.handleContentContextMenuCommand(event);
break;
case "SyncedTabsSidebarTabsFilterContext":
this.handleTabsFilterContextMenuCommand(event);
break;
}
break;
}
}
},
handleTabsFilterContextMenuShown(event) {
let document = event.target.ownerDocument;
let focusedElement = document.commandDispatcher.focusedElement;
if (focusedElement != this.tabsFilter) {
this.tabsFilter.focus();
}
for (let item of event.target.children) {
if (!item.hasAttribute("cmd")) {
continue;
}
let command = item.getAttribute("cmd");
let controller = document.commandDispatcher.getControllerForCommand(command);
if (controller.isCommandEnabled(command)) {
item.removeAttribute("disabled");
} else {
item.setAttribute("disabled", "true");
}
}
},
handleContentContextMenuCommand(event) {
@ -357,19 +411,33 @@ TabListView.prototype = {
this.onBookmarkTab();
break;
case "syncedTabsRefresh":
case "syncedTabsRefreshFilter":
this.props.onSyncRefresh();
break;
}
},
handleContentContextMenu(event) {
let itemNode = this._findParentItemNode(event.target);
if (itemNode) {
this._selectRow(itemNode);
handleTabsFilterContextMenuCommand(event) {
let command = event.target.getAttribute("cmd");
let dispatcher = getChromeWindow(this._window).document.commandDispatcher;
let controller = dispatcher.focusedElement.controllers.getControllerForCommand(command);
controller.doCommand(command);
},
handleContextMenu(event) {
let menu;
if (event.target == this.tabsFilter) {
menu = getTabsFilterContextMenu(this._window);
} else {
let itemNode = this._findParentItemNode(event.target);
if (itemNode) {
this._selectRow(itemNode);
}
menu = getContextMenu(this._window);
this.adjustContextMenu(menu);
}
let menu = getContextMenu(this._window);
this.adjustContextMenu(menu);
menu.openPopupAtScreen(event.screenX, event.screenY, true, event);
},

View File

@ -255,6 +255,78 @@ add_task(function* testSyncedTabsSidebarStatus() {
add_task(testClean);
add_task(function* testSyncedTabsSidebarContextMenu() {
yield SidebarUI.show('viewTabsSidebar');
let syncedTabsDeckComponent = window.SidebarUI.browser.contentWindow.syncedTabsDeckComponent;
let SyncedTabs = window.SidebarUI.browser.contentWindow.SyncedTabs;
Assert.ok(syncedTabsDeckComponent, "component exists");
originalSyncedTabsInternal = SyncedTabs._internal;
SyncedTabs._internal = {
isConfiguredToSyncTabs: true,
hasSyncedThisSession: true,
getTabClients() { return Promise.resolve([])},
syncTabs() {return Promise.resolve();},
};
sinon.stub(syncedTabsDeckComponent, "_accountStatus", ()=> Promise.resolve(true));
sinon.stub(SyncedTabs._internal, "getTabClients", ()=> Promise.resolve(Cu.cloneInto(FIXTURE, {})));
yield syncedTabsDeckComponent.updatePanel();
// This is a hacky way of waiting for the view to render. The view renders
// after the following promise (a different instance of which is triggered
// in updatePanel) resolves, so we wait for it here as well
yield syncedTabsDeckComponent.tabListComponent._store.getData();
info("Right-clicking the search box should show text-related actions");
let filterMenuItems = [
"menuitem[cmd=cmd_undo]",
"menuseparator",
// We don't check whether the commands are enabled due to platform
// differences. On OS X and Windows, "cut" and "copy" are always enabled
// for HTML inputs; on Linux, they're only enabled if text is selected.
"menuitem[cmd=cmd_cut]",
"menuitem[cmd=cmd_copy]",
"menuitem[cmd=cmd_paste]",
"menuitem[cmd=cmd_delete]",
"menuseparator",
"menuitem[cmd=cmd_selectAll]",
"menuseparator",
"menuitem#syncedTabsRefreshFilter",
];
yield* testContextMenu(syncedTabsDeckComponent,
"#SyncedTabsSidebarTabsFilterContext",
".tabsFilter",
filterMenuItems);
info("Right-clicking a tab should show additional actions");
let tabMenuItems = [
["menuitem#syncedTabsOpenSelected", { hidden: false }],
["menuitem#syncedTabsBookmarkSelected", { hidden: false }],
["menuseparator", { hidden: false }],
["menuitem#syncedTabsRefresh", { hidden: false }],
];
yield* testContextMenu(syncedTabsDeckComponent,
"#SyncedTabsSidebarContext",
"#tab-7cqCr77ptzX3-0",
tabMenuItems);
info("Right-clicking a client shouldn't show any actions");
let sidebarMenuItems = [
["menuitem#syncedTabsOpenSelected", { hidden: true }],
["menuitem#syncedTabsBookmarkSelected", { hidden: true }],
["menuseparator", { hidden: true }],
["menuitem#syncedTabsRefresh", { hidden: false }],
];
yield* testContextMenu(syncedTabsDeckComponent,
"#SyncedTabsSidebarContext",
"#item-OL3EJCsdb2JD",
sidebarMenuItems);
});
add_task(testClean);
function checkItem(node, item) {
Assert.ok(node.classList.contains("item"),
"Node should have .item class");
@ -279,3 +351,47 @@ function checkItem(node, item) {
}
}
function* testContextMenu(syncedTabsDeckComponent, contextSelector, triggerSelector, menuSelectors) {
let contextMenu = document.querySelector(contextSelector);
let triggerElement = syncedTabsDeckComponent.container.querySelector(triggerSelector);
let promisePopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
let chromeWindow = triggerElement.ownerDocument.defaultView.top;
let rect = triggerElement.getBoundingClientRect();
let contentRect = chromeWindow.SidebarUI.browser.getBoundingClientRect();
// The offsets in `rect` are relative to the content window, but
// `synthesizeMouseAtPoint` calls `nsIDOMWindowUtils.sendMouseEvent`,
// which interprets the offsets relative to the containing *chrome* window.
// This means we need to account for the width and height of any elements
// outside the `browser` element, like `sidebarheader`.
let offsetX = contentRect.x + rect.x + (rect.width / 2);
let offsetY = contentRect.y + rect.y + (rect.height / 4);
yield EventUtils.synthesizeMouseAtPoint(offsetX, offsetY, {
type: "contextmenu",
button: 2,
}, chromeWindow);
yield promisePopupShown;
checkChildren(contextMenu, menuSelectors);
let promisePopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
contextMenu.hidePopup();
yield promisePopupHidden;
}
function checkChildren(node, selectors) {
is(node.children.length, selectors.length, "Menu item count doesn't match");
for (let index = 0; index < node.children.length; index++) {
let child = node.children[index];
let [selector, props] = [].concat(selectors[index]);
ok(selector, `Node at ${index} should have selector`);
ok(child.matches(selector), `Node ${
index} should match ${selector}`);
if (props) {
Object.keys(props).forEach(prop => {
is(child[prop], props[prop], `${prop} value at ${index} should match`);
});
}
}
}

View File

@ -83,9 +83,11 @@ this.Feeds = {
if (aIsFeed) {
// re-create the principal as it may be a CPOW.
// once this can't be a CPOW anymore, we should just use aPrincipal instead
// of creating a new one.
let principalURI = BrowserUtils.makeURIFromCPOW(aPrincipal.URI);
let principalToCheck =
Services.scriptSecurityManager.createCodebasePrincipal(principalURI, {});
Services.scriptSecurityManager.createCodebasePrincipal(principalURI, aPrincipal.originAttributes);
try {
BrowserUtils.urlSecurityCheck(aLink.href, principalToCheck,
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);

View File

@ -123,17 +123,3 @@ idea {
task wrapper(type: Wrapper) {
}
// From http://jdpgrailsdev.github.io/blog/2014/10/28/gradle_resolve_all_dependencies.html.
task resolveDependencies {
doLast {
project.rootProject.allprojects.each { subProject ->
subProject.buildscript.configurations.each { configuration ->
configuration.resolve()
}
subProject.configurations.each { configuration ->
configuration.resolve()
}
}
}
}

View File

@ -42,9 +42,6 @@ ifdef MOZTTDIR
# Install the Firefox OS fonts.
include $(MOZTTDIR)/fonts.mk
MOZTT_DEST = $(FINAL_TARGET)/fonts
ifdef MOZ_B2GDROID
MOZTT_DEST = $(FINAL_TARGET)/res/fonts
endif
MOZTT_FILES = $(patsubst external/moztt/%,$(MOZTTDIR)/%,$(filter external/moztt/%,$(subst :, ,$(PRODUCT_COPY_FILES))))
INSTALL_TARGETS += MOZTT
endif

View File

@ -3,15 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env browser */
/* globals AddonsTab, WorkersTab */
"use strict";
const Services = require("Services");
const { createFactory, createClass, DOM: dom } =
require("devtools/client/shared/vendor/react");
const Services = require("Services");
const TabMenu = createFactory(require("./tab-menu"));
loader.lazyGetter(this, "AddonsTab",
() => createFactory(require("./addons-tab")));
loader.lazyGetter(this, "WorkersTab",
@ -65,8 +66,9 @@ module.exports = createClass({
return dom.div({ className: "app" },
TabMenu({ tabs, selectedTabId, selectTab }),
dom.div({ className: "main-content" },
selectedTab.component({ client }))
);
selectedTab.component({ client })
)
);
},
onHashChange() {

View File

@ -0,0 +1,47 @@
/* 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/. */
/* eslint-env browser */
/* globals BrowserToolboxProcess */
"use strict";
loader.lazyImporter(this, "BrowserToolboxProcess",
"resource://devtools/client/framework/ToolboxProcess.jsm");
const { createClass, DOM: dom } =
require("devtools/client/shared/vendor/react");
const Services = require("Services");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
module.exports = createClass({
displayName: "AddonTarget",
render() {
let { target, debugDisabled } = this.props;
return dom.div({ className: "target" },
dom.img({
className: "target-icon",
role: "presentation",
src: target.icon
}),
dom.div({ className: "target-details" },
dom.div({ className: "target-name" }, target.name)
),
dom.button({
className: "debug-button",
onClick: this.debug,
disabled: debugDisabled,
}, Strings.GetStringFromName("debug"))
);
},
debug() {
let { target } = this.props;
BrowserToolboxProcess.init({ addonID: target.addonID });
},
});

View File

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env browser */
/* globals AddonManager */
"use strict";
@ -10,10 +11,10 @@ loader.lazyImporter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
const { Cc, Ci } = require("chrome");
const { createClass, DOM: dom } =
require("devtools/client/shared/vendor/react");
const Services = require("Services");
const { createClass, DOM: dom } = require("devtools/client/shared/vendor/react");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");

View File

@ -5,18 +5,19 @@
"use strict";
const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
const Services = require("Services");
const { createFactory, createClass, DOM: dom } =
require("devtools/client/shared/vendor/react");
const Services = require("Services");
const AddonsControls = createFactory(require("./addons-controls"));
const AddonTarget = createFactory(require("./addon-target"));
const TabHeader = createFactory(require("./tab-header"));
const TargetList = createFactory(require("./target-list"));
const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
@ -54,18 +55,22 @@ module.exports = createClass({
let { client } = this.props;
let { debugDisabled, extensions: targets } = this.state;
let name = Strings.GetStringFromName("extensions");
let targetClass = AddonTarget;
return dom.div({
id: "tab-addons",
className: "tab",
role: "tabpanel",
"aria-labelledby": "tab-addons-header-name" },
"aria-labelledby": "tab-addons-header-name"
},
TabHeader({
id: "tab-addons-header-name",
name: Strings.GetStringFromName("addons") }),
name: Strings.GetStringFromName("addons")
}),
AddonsControls({ debugDisabled }),
dom.div({ id: "addons" },
TargetList({ name, targets, client, debugDisabled })));
TargetList({ name, targets, client, debugDisabled, targetClass })
));
},
updateDebugStatus() {
@ -82,10 +87,10 @@ module.exports = createClass({
return {
name: addon.name,
icon: addon.iconURL || ExtensionIcon,
type: addon.type,
addonID: addon.id
};
});
this.setState({ extensions });
});
},

View File

@ -4,12 +4,13 @@
DevToolsModules(
'aboutdebugging.js',
'addon-target.js',
'addons-controls.js',
'addons-tab.js',
'tab-header.js',
'tab-menu-entry.js',
'tab-menu.js',
'target-list.js',
'target.js',
'worker-target.js',
'workers-tab.js',
)

View File

@ -4,11 +4,9 @@
"use strict";
const Services = require("Services");
const { createClass, createFactory, DOM: dom } =
const { createClass, DOM: dom } =
require("devtools/client/shared/vendor/react");
const Target = createFactory(require("./target"));
const Services = require("Services");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
@ -21,18 +19,16 @@ module.exports = createClass({
displayName: "TargetList",
render() {
let { client, debugDisabled } = this.props;
let { client, debugDisabled, targetClass } = this.props;
let targets = this.props.targets.sort(LocaleCompare).map(target => {
return Target({ client, target, debugDisabled });
return targetClass({ client, target, debugDisabled });
});
return (
dom.div({ id: this.props.id, className: "targets" },
dom.h4(null, this.props.name),
targets.length > 0 ?
targets :
dom.p(null, Strings.GetStringFromName("nothing"))
)
return dom.div({ id: this.props.id, className: "targets" },
dom.h4(null, this.props.name),
targets.length > 0 ?
targets :
dom.p(null, Strings.GetStringFromName("nothing"))
);
},
});

View File

@ -3,38 +3,38 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env browser */
/* globals gDevTools, TargetFactory, Toolbox */
"use strict";
loader.lazyRequireGetter(this, "TargetFactory",
"devtools/client/framework/target", true);
loader.lazyRequireGetter(this, "gDevTools",
"devtools/client/framework/devtools", true);
"devtools/client/framework/devtools", true);
loader.lazyRequireGetter(this, "TargetFactory",
"devtools/client/framework/target", true);
loader.lazyRequireGetter(this, "Toolbox",
"devtools/client/framework/toolbox", true);
"devtools/client/framework/toolbox", true);
loader.lazyImporter(this, "BrowserToolboxProcess",
"resource://devtools/client/framework/ToolboxProcess.jsm");
const Services = require("Services");
const { createClass, DOM: dom } =
require("devtools/client/shared/vendor/react");
const Services = require("Services");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
module.exports = createClass({
displayName: "Target",
displayName: "WorkerTarget",
render() {
let { target, debugDisabled } = this.props;
let isServiceWorker = (target.type === "serviceworker");
let isRunning = (!isServiceWorker || target.workerActor);
let isRunning = this.isRunning();
let isServiceWorker = this.isServiceWorker();
return dom.div({ className: "target" },
dom.img({
className: "target-icon",
role: "presentation",
src: target.icon }),
src: target.icon
}),
dom.div({ className: "target-details" },
dom.div({ className: "target-name" }, target.name)
),
@ -49,7 +49,7 @@ module.exports = createClass({
dom.button({
className: "debug-button",
onClick: this.debug,
disabled: debugDisabled,
disabled: debugDisabled
}, Strings.GetStringFromName("debug")) :
dom.button({
className: "start-button",
@ -60,56 +60,52 @@ module.exports = createClass({
},
debug() {
let { target } = this.props;
switch (target.type) {
case "extension":
BrowserToolboxProcess.init({ addonID: target.addonID });
break;
case "serviceworker":
if (target.workerActor) {
this.openWorkerToolbox(target.workerActor);
}
break;
case "sharedworker":
this.openWorkerToolbox(target.workerActor);
break;
case "worker":
this.openWorkerToolbox(target.workerActor);
break;
default:
window.alert("Not implemented yet!");
break;
}
},
push() {
let { client, target } = this.props;
if (target.workerActor) {
client.request({
to: target.workerActor,
type: "push"
});
if (!this.isRunning()) {
// If the worker is not running, we can't debug it.
return;
}
},
start() {
let { client, target } = this.props;
if (target.type === "serviceworker" && !target.workerActor) {
client.request({
to: target.registrationActor,
type: "start"
});
}
},
openWorkerToolbox(workerActor) {
let { client } = this.props;
client.attachWorker(workerActor, (response, workerClient) => {
gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
"jsdebugger", Toolbox.HostType.WINDOW)
client.attachWorker(target.workerActor, (response, workerClient) => {
let workerTarget = TargetFactory.forWorker(workerClient);
gDevTools.showToolbox(workerTarget, "jsdebugger", Toolbox.HostType.WINDOW)
.then(toolbox => {
toolbox.once("destroy", () => workerClient.detach());
});
});
},
push() {
let { client, target } = this.props;
if (!this.isRunning()) {
// If the worker is not running, we can't push to it.
return;
}
client.request({
to: target.workerActor,
type: "push"
});
},
start() {
let { client, target } = this.props;
if (!this.isServiceWorker() || this.isRunning()) {
// Either the worker is already running, or it's not a service worker, in
// which case we don't know how to start it.
return;
}
client.request({
to: target.registrationActor,
type: "start"
});
},
isRunning() {
// We know the target is running if it has a worker actor.
return !!this.props.target.workerActor;
},
isServiceWorker() {
// We know the target is a service worker if it has a registration actor.
return !!this.props.target.registrationActor;
},
});

View File

@ -5,16 +5,18 @@
"use strict";
const { Ci } = require("chrome");
const { createClass, createFactory, DOM: dom } =
require("devtools/client/shared/vendor/react");
const { Task } = require("resource://gre/modules/Task.jsm");
const Services = require("Services");
const { createClass, createFactory, DOM: dom } =
require("devtools/client/shared/vendor/react");
const TabHeader = createFactory(require("./tab-header"));
const TargetList = createFactory(require("./target-list"));
const WorkerTarget = createFactory(require("./worker-target"));
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg";
module.exports = createClass({
@ -48,31 +50,41 @@ module.exports = createClass({
render() {
let { client } = this.props;
let { workers } = this.state;
let targetClass = WorkerTarget;
return dom.div({
id: "tab-workers",
className: "tab",
role: "tabpanel",
"aria-labelledby": "tab-workers-header-name" },
"aria-labelledby": "tab-workers-header-name"
},
TabHeader({
id: "tab-workers-header-name",
name: Strings.GetStringFromName("workers") }),
name: Strings.GetStringFromName("workers")
}),
dom.div({ id: "workers", className: "inverted-icons" },
TargetList({
client,
id: "service-workers",
name: Strings.GetStringFromName("serviceWorkers"),
targets: workers.service,
client }),
targetClass,
targets: workers.service
}),
TargetList({
client,
id: "shared-workers",
name: Strings.GetStringFromName("sharedWorkers"),
targets: workers.shared,
client }),
targetClass,
targets: workers.shared
}),
TargetList({
client,
id: "other-workers",
name: Strings.GetStringFromName("otherWorkers"),
targets: workers.other,
client })));
targetClass,
targets: workers.other
})
));
},
update() {
@ -81,7 +93,6 @@ module.exports = createClass({
this.getWorkerForms().then(forms => {
forms.registrations.forEach(form => {
workers.service.push({
type: "serviceworker",
icon: WorkerIcon,
name: form.url,
url: form.url,
@ -92,7 +103,6 @@ module.exports = createClass({
forms.workers.forEach(form => {
let worker = {
type: "worker",
icon: WorkerIcon,
name: form.url,
url: form.url,
@ -113,7 +123,6 @@ module.exports = createClass({
}
break;
case Ci.nsIWorkerDebugger.TYPE_SHARED:
worker.type = "sharedworker";
workers.shared.push(worker);
break;
default:

View File

@ -3,11 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env browser */
/* globals DebuggerClient, DebuggerServer, Telemetry */
"use strict";
const { loader } = Components.utils.import(
"resource://devtools/shared/Loader.jsm", {});
const { BrowserLoader } = Components.utils.import(
"resource://devtools/client/shared/browser-loader.js", {});
loader.lazyRequireGetter(this, "DebuggerClient",
"devtools/shared/client/main", true);
@ -16,17 +19,14 @@ loader.lazyRequireGetter(this, "DebuggerServer",
loader.lazyRequireGetter(this, "Telemetry",
"devtools/client/shared/telemetry");
const { BrowserLoader } = Components.utils.import(
"resource://devtools/client/shared/browser-loader.js", {});
const { require } = BrowserLoader({
baseURI: "resource://devtools/client/aboutdebugging/",
window
});
const {
createFactory,
render,
unmountComponentAtNode } = require("devtools/client/shared/vendor/react");
const { createFactory, render, unmountComponentAtNode } =
require("devtools/client/shared/vendor/react");
const AboutDebuggingApp = createFactory(require("./components/aboutdebugging"));
var AboutDebugging = {
@ -36,6 +36,7 @@ var AboutDebugging = {
DebuggerServer.addBrowserActors();
}
DebuggerServer.allowChromeProcess = true;
this.client = new DebuggerClient(DebuggerServer.connectPipe());
this.client.connect().then(() => {

View File

@ -12,6 +12,17 @@ const initialState = Immutable({
breakpoints: {}
});
// Return the first argument that is a string, or null if nothing is a
// string.
function firstString(...args) {
for (var arg of args) {
if (typeof arg === "string") {
return arg;
}
}
return null;
}
function update(state = initialState, action, emitChange) {
switch(action.type) {
case constants.ADD_BREAKPOINT: {
@ -24,7 +35,10 @@ function update(state = initialState, action, emitChange) {
state = setIn(state, ['breakpoints', id], bp.merge({
disabled: false,
loading: true,
condition: action.condition || bp.condition || undefined
// We want to do an OR here, but we can't because we need
// empty strings to be truthy, i.e. an empty string is a valid
// condition.
condition: firstString(action.condition, bp.condition)
}));
emitChange(existingBp ? "breakpoint-enabled" : "breakpoint-added",

View File

@ -120,6 +120,13 @@ function test() {
gDebugger);
}
function waitForConditionUpdate() {
// This will close the popup and send another request to update
// the condition
gSources._hideConditionalPopup();
return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
}
Task.spawn(function*() {
yield waitForSourceAndCaretAndScopes(gPanel, ".html", 17);
@ -142,8 +149,10 @@ function test() {
testBreakpoint(19, false, undefined);
yield modBreakpoint2();
testBreakpoint(19, true, undefined);
yield waitForConditionUpdate();
yield addBreakpoint3();
testBreakpoint(20, false, undefined);
testBreakpoint(20, true, "");
yield waitForConditionUpdate();
yield modBreakpoint3();
testBreakpoint(20, false, "bamboocha");
yield addBreakpoint4();

View File

@ -5,7 +5,7 @@
/**
* Make sure that conditional breakpoints with blank expressions
* are stored as plain breakpoints when re-enabling them.
* maintain their conditions after enabling them.
*/
const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
@ -35,7 +35,7 @@ function test() {
yield actions.addBreakpoint(location);
const bp = queries.getBreakpoint(getState(), location);
is(bp.condition, undefined, "The conditional expression is correct.");
is(bp.condition, "", "The conditional expression is correct.");
// Reset traits back to default value
client.mainRoot.traits.conditionalBreakpoints = true;

View File

@ -115,6 +115,13 @@ function test() {
gDebugger);
}
function waitForConditionUpdate() {
// This will close the popup and send another request to update
// the condition
gSources._hideConditionalPopup();
return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
}
Task.spawn(function*() {
yield waitForSourceAndCaretAndScopes(gPanel, ".html", 17);
@ -137,8 +144,10 @@ function test() {
testBreakpoint(19, false, undefined);
yield modBreakpoint2();
testBreakpoint(19, true, undefined);
yield waitForConditionUpdate();
yield addBreakpoint3();
testBreakpoint(20, false, undefined);
testBreakpoint(20, true, "");
yield waitForConditionUpdate();
yield modBreakpoint3();
testBreakpoint(20, false, "bamboocha");
yield addBreakpoint4();

View File

@ -5,7 +5,7 @@
/**
* Make sure that conditional breakpoints with undefined expressions
* are stored as plain breakpoints when re-enabling them (with
* maintain their conditions when re-enabling them (with
* server-side support)
*/
@ -31,7 +31,7 @@ function test() {
yield actions.addBreakpoint(location);
const bp = queries.getBreakpoint(getState(), location);
is(bp.condition, undefined, "The conditional expression is correct.");
is(bp.condition, "", "The conditional expression is correct.");
resumeDebuggerThenCloseAndFinish(gPanel);
});

View File

@ -7,21 +7,21 @@
"use strict";
define(function(require, exports, module) {
const React = require("devtools/client/shared/vendor/react");
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
const { Headers } = createFactories(require("./headers"));
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
const DOM = React.DOM;
const { div } = dom;
/**
* This template represents the 'Headers' panel
* s responsible for rendering its content.
*/
let HeadersPanel = React.createClass({
let HeadersPanel = createClass({
propTypes: {
actions: React.PropTypes.object,
data: React.PropTypes.object,
actions: PropTypes.object,
data: PropTypes.object,
},
displayName: "HeadersPanel",
@ -36,9 +36,9 @@ define(function(require, exports, module) {
let data = this.props.data;
return (
DOM.div({className: "headersPanelBox"},
div({className: "headersPanelBox"},
HeadersToolbar({actions: this.props.actions}),
DOM.div({className: "panelContent"},
div({className: "panelContent"},
Headers({data: data})
)
)
@ -50,9 +50,9 @@ define(function(require, exports, module) {
* This template is responsible for rendering a toolbar
* within the 'Headers' panel.
*/
let HeadersToolbar = React.createFactory(React.createClass({
let HeadersToolbar = createFactory(createClass({
propTypes: {
actions: React.PropTypes.object,
actions: PropTypes.object,
},
displayName: "HeadersToolbar",

View File

@ -7,18 +7,16 @@
"use strict";
define(function(require, exports, module) {
const React = require("devtools/client/shared/vendor/react");
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
// Constants
const DOM = React.DOM;
const PropTypes = React.PropTypes;
const { div, span, table, tbody, tr, td, code } = dom;
/**
* This template is responsible for rendering basic layout
* of the 'Headers' panel. It displays HTTP headers groups such as
* received or response headers.
*/
let Headers = React.createClass({
let Headers = createClass({
propTypes: {
data: PropTypes.object,
},
@ -33,24 +31,24 @@ define(function(require, exports, module) {
let data = this.props.data;
return (
DOM.div({className: "netInfoHeadersTable"},
DOM.div({className: "netHeadersGroup"},
DOM.div({className: "netInfoHeadersGroup"},
DOM.span({className: "netHeader twisty"},
div({className: "netInfoHeadersTable"},
div({className: "netHeadersGroup"},
div({className: "netInfoHeadersGroup"},
span({className: "netHeader twisty"},
Locale.$STR("jsonViewer.responseHeaders")
)
),
DOM.table({cellPadding: 0, cellSpacing: 0},
table({cellPadding: 0, cellSpacing: 0},
HeaderList({headers: data.response})
)
),
DOM.div({className: "netHeadersGroup"},
DOM.div({className: "netInfoHeadersGroup"},
DOM.span({className: "netHeader twisty"},
div({className: "netHeadersGroup"},
div({className: "netInfoHeadersGroup"},
span({className: "netHeader twisty"},
Locale.$STR("jsonViewer.requestHeaders")
)
),
DOM.table({cellPadding: 0, cellSpacing: 0},
table({cellPadding: 0, cellSpacing: 0},
HeaderList({headers: data.request})
)
)
@ -63,7 +61,7 @@ define(function(require, exports, module) {
* This template renders headers list,
* name + value pairs.
*/
let HeaderList = React.createFactory(React.createClass({
let HeaderList = createFactory(createClass({
propTypes: {
headers: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
@ -89,19 +87,19 @@ define(function(require, exports, module) {
let rows = [];
headers.forEach(header => {
rows.push(
DOM.tr({key: header.name},
DOM.td({className: "netInfoParamName"},
DOM.span({title: header.name}, header.name)
tr({key: header.name},
td({className: "netInfoParamName"},
span({title: header.name}, header.name)
),
DOM.td({className: "netInfoParamValue"},
DOM.code({}, header.value)
td({className: "netInfoParamValue"},
code({}, header.value)
)
)
);
});
return (
DOM.tbody({},
tbody({},
rows
)
);

View File

@ -7,27 +7,29 @@
"use strict";
define(function(require, exports, module) {
const React = require("devtools/client/shared/vendor/react");
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
const { TreeView } = createFactories(require("./reps/tree-view"));
const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
const { SearchBox } = createFactories(require("./search-box"));
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
const DOM = React.DOM;
const { div } = dom;
/**
* This template represents the 'JSON' panel. The panel is
* responsible for rendering an expandable tree that allows simple
* inspection of JSON structure.
*/
let JsonPanel = React.createClass({
let JsonPanel = createClass({
propTypes: {
data: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.array,
React.PropTypes.object
data: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
]),
searchFilter: React.PropTypes.string,
actions: React.PropTypes.object,
searchFilter: PropTypes.string,
actions: PropTypes.object,
},
displayName: "JsonPanel",
@ -48,32 +50,53 @@ define(function(require, exports, module) {
// XXX shortcut for focusing the Filter field (see Bug 1178771).
},
onFilter: function(object) {
if (!this.props.searchFilter) {
return true;
}
let json = JSON.stringify(object).toLowerCase();
return json.indexOf(this.props.searchFilter) >= 0;
},
render: function() {
let content;
let data = this.props.data;
// Append custom column for displaying values. This column
// Take all available horizontal space.
let columns = [{
id: "value",
width: "100%"
}];
try {
if (typeof data == "object") {
// Render tree component. Use Reps to render JSON values.
content = TreeView({
data: this.props.data,
object: this.props.data,
mode: "tiny",
searchFilter: this.props.searchFilter
onFilter: this.onFilter.bind(this),
columns: columns,
renderValue: props => {
return Rep(props);
}
});
} else {
content = DOM.div({className: "jsonParseError"},
content = div({className: "jsonParseError"},
data + ""
);
}
} catch (err) {
content = DOM.div({className: "jsonParseError"},
content = div({className: "jsonParseError"},
err + ""
);
}
return (
DOM.div({className: "jsonPanelBox"},
div({className: "jsonPanelBox"},
JsonToolbar({actions: this.props.actions}),
DOM.div({className: "panelContent"},
div({className: "panelContent"},
content
)
)
@ -84,9 +107,9 @@ define(function(require, exports, module) {
/**
* This template represents a toolbar within the 'JSON' panel.
*/
let JsonToolbar = React.createFactory(React.createClass({
let JsonToolbar = createFactory(createClass({
propTypes: {
actions: React.PropTypes.object,
actions: PropTypes.object,
},
displayName: "JsonToolbar",

View File

@ -7,7 +7,7 @@
"use strict";
define(function(require, exports, module) {
const React = require("devtools/client/shared/vendor/react");
const { createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
const { JsonPanel } = createFactories(require("./json-panel"));
const { TextPanel } = createFactories(require("./text-panel"));
@ -18,17 +18,17 @@ define(function(require, exports, module) {
* This object represents the root application template
* responsible for rendering the basic tab layout.
*/
let MainTabbedArea = React.createClass({
let MainTabbedArea = createClass({
propTypes: {
jsonText: React.PropTypes.string,
tabActive: React.PropTypes.number,
actions: React.PropTypes.object,
headers: React.PropTypes.object,
searchFilter: React.PropTypes.string,
json: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.object,
React.PropTypes.array
jsonText: PropTypes.string,
tabActive: PropTypes.number,
actions: PropTypes.object,
headers: PropTypes.object,
searchFilter: PropTypes.string,
json: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.array
])
},

View File

@ -7,5 +7,4 @@
DevToolsModules(
'tabs.js',
'toolbar.js',
'tree-view.js',
)

View File

@ -1,267 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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";
define(function(require, exports, module) {
// Dependencies
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
const { StringRep } = require("devtools/client/shared/components/reps/string");
const DOM = React.DOM;
let uid = 0;
/**
* Renders a tree view with expandable/collapsible items.
*/
let TreeView = React.createClass({
propTypes: {
searchFilter: React.PropTypes.string,
data: React.PropTypes.any,
mode: React.PropTypes.string,
},
displayName: "TreeView",
getInitialState: function() {
return {
data: {},
searchFilter: null
};
},
// Data
componentDidMount: function() {
let members = initMembers(this.props.data, 0);
this.setState({ // eslint-disable-line
data: members,
searchFilter:
this.props.searchFilter
});
},
componentWillReceiveProps: function(nextProps) {
let updatedState = {
searchFilter: nextProps.searchFilter
};
if (this.props.data !== nextProps.data) {
updatedState.data = initMembers(nextProps.data, 0);
}
this.setState(updatedState);
},
// Rendering
render: function() {
let mode = this.props.mode;
let root = this.state.data;
let children = [];
if (Array.isArray(root)) {
for (let i = 0; i < root.length; i++) {
let child = root[i];
children.push(TreeNode({
key: child.key,
data: child,
mode: mode,
searchFilter: this.state.searchFilter || this.props.searchFilter
}));
}
} else {
children.push(React.addons.createFragment(root));
}
return (
DOM.div({className: "domTable", cellPadding: 0, cellSpacing: 0},
children
)
);
},
});
/**
* Represents a node within the tree.
*/
let TreeNode = React.createFactory(React.createClass({
propTypes: {
searchFilter: React.PropTypes.string,
data: React.PropTypes.object,
mode: React.PropTypes.string,
},
displayName: "TreeNode",
getInitialState: function() {
return {
data: this.props.data,
searchFilter: null
};
},
onClick: function(e) {
let member = this.state.data;
member.open = !member.open;
this.setState({data: member});
e.stopPropagation();
},
render: function() {
let member = this.state.data;
let mode = this.props.mode;
let classNames = ["memberRow"];
classNames.push(member.type + "Row");
if (member.hasChildren) {
classNames.push("hasChildren");
}
if (member.open) {
classNames.push("opened");
}
if (!member.children) {
// Cropped strings are expandable, but they don't have children.
let isString = typeof (member.value) == "string";
if (member.hasChildren && !isString) {
member.children = initMembers(member.value);
} else {
member.children = [];
}
}
let children = [];
if (member.open && member.children.length) {
for (let i in member.children) {
let child = member.children[i];
children.push(TreeNode({
key: child.key,
data: child,
mode: mode,
searchFilter: this.state.searchFilter || this.props.searchFilter
}));
}
}
let filter = this.props.searchFilter || "";
let name = member.name || "";
let value = member.value || "";
// Filtering is case-insensitive
filter = filter.toLowerCase();
name = name.toLowerCase();
if (filter && (name.indexOf(filter) < 0)) {
// Cache the stringify result, so the filtering is fast
// the next time.
if (!member.valueString) {
member.valueString = JSON.stringify(value).toLowerCase();
}
if (member.valueString && member.valueString.indexOf(filter) < 0) {
classNames.push("hidden");
}
}
return (
DOM.div({className: classNames.join(" ")},
DOM.span({className: "memberLabelCell", onClick: this.onClick},
DOM.span({className: "memberIcon"}),
DOM.span({className: "memberLabel " + member.type + "Label"},
member.name)
),
DOM.span({className: "memberValueCell"},
DOM.span({},
Rep({
object: member.value,
mode: this.props.mode,
member: member
})
)
),
DOM.div({className: "memberChildren"},
children
)
)
);
},
}));
// Helpers
function initMembers(parent) {
let members = getMembers(parent);
return members;
}
function getMembers(object) {
let members = [];
getObjectProperties(object, function(prop, value) {
let valueType = typeof (value);
let hasChildren = (valueType === "object" && hasProperties(value));
// Cropped strings are expandable, so the user can see the
// entire original value.
if (StringRep.isCropped(value)) {
hasChildren = true;
}
let type = getType(value);
let member = createMember(type, prop, value, hasChildren);
members.push(member);
});
return members;
}
function createMember(type, name, value, hasChildren) {
let member = {
name: name,
type: type,
rowClass: "memberRow-" + type,
hasChildren: hasChildren,
value: value,
open: false,
key: uid++
};
return member;
}
function getObjectProperties(obj, callback) {
for (let p in obj) {
try {
callback.call(this, p, obj[p]);
} catch (e) {
// Ignore
}
}
}
function hasProperties(obj) {
if (typeof (obj) == "string") {
return false;
}
return Object.keys(obj).length > 1;
}
function getType(object) {
// A type provider (or a decorator) should be used here.
return "dom";
}
// Exports from this module
exports.TreeView = TreeView;
});

View File

@ -7,9 +7,9 @@
"use strict";
define(function(require, exports, module) {
const React = require("devtools/client/shared/vendor/react");
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const DOM = React.DOM;
const { input } = dom;
// For smooth incremental searching (in case the user is typing quickly).
const searchDelay = 250;
@ -18,9 +18,9 @@ define(function(require, exports, module) {
* This object represents a search box located at the
* top right corner of the application.
*/
let SearchBox = React.createClass({
let SearchBox = createClass({
propTypes: {
actions: React.PropTypes.object,
actions: PropTypes.object,
},
displayName: "SearchBox",
@ -43,7 +43,7 @@ define(function(require, exports, module) {
render: function() {
return (
DOM.input({className: "searchBox",
input({className: "searchBox",
placeholder: Locale.$STR("jsonViewer.filterJSON"),
onChange: this.onSearch})
);

View File

@ -7,19 +7,19 @@
"use strict";
define(function(require, exports, module) {
const React = require("devtools/client/shared/vendor/react");
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
const DOM = React.DOM;
const { div, pre } = dom;
/**
* This template represents the 'Raw Data' panel displaying
* JSON as a text received from the server.
*/
let TextPanel = React.createClass({
let TextPanel = createClass({
propTypes: {
actions: React.PropTypes.object,
data: React.PropTypes.string
actions: PropTypes.object,
data: PropTypes.string
},
displayName: "TextPanel",
@ -30,10 +30,10 @@ define(function(require, exports, module) {
render: function() {
return (
DOM.div({className: "textPanelBox"},
div({className: "textPanelBox"},
TextToolbar({actions: this.props.actions}),
DOM.div({className: "panelContent"},
DOM.pre({className: "data"},
div({className: "panelContent"},
pre({className: "data"},
this.props.data
)
)
@ -46,9 +46,9 @@ define(function(require, exports, module) {
* This object represents a toolbar displayed within the
* 'Raw Data' panel.
*/
let TextToolbar = React.createFactory(React.createClass({
let TextToolbar = createFactory(createClass({
propTypes: {
actions: React.PropTypes.object,
actions: PropTypes.object,
},
displayName: "TextToolbar",

View File

@ -209,6 +209,7 @@ let Converter = Class({
let clientBaseUrl = "resource://devtools/client/";
let baseUrl = clientBaseUrl + "jsonview/";
let themeVarsUrl = clientBaseUrl + "themes/variables.css";
let commonUrl = clientBaseUrl + "themes/common.css";
return "<!DOCTYPE html>\n" +
"<html class=\"" + themeClassName + "\">" +
@ -216,6 +217,8 @@ let Converter = Class({
"<base href=\"" + this.htmlEncode(baseUrl) + "\">" +
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
themeVarsUrl + "\">" +
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
commonUrl + "\">" +
"<link rel=\"stylesheet\" type=\"text/css\" href=\"css/main.css\">" +
"<script data-main=\"viewer-config\" src=\"lib/require.js\"></script>" +
"</head><body>" +

View File

@ -1,216 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.domTable {
font-size: 11px;
font-family: Lucida Grande, Tahoma, sans-serif;
line-height: 15px;
width: 100%;
}
.domTable > tbody > tr > td {
border-bottom: 1px solid #EFEFEF;
}
.hidden {
display: none;
}
.memberLabelCell {
padding: 2px 0 2px 0px;
}
.memberValueCell {
padding: 2px 0 2px 5px;
overflow: hidden;
}
.memberLabel {
cursor: default;
overflow: hidden;
white-space: nowrap;
}
.memberLabelPrefix {
color: gray;
margin-right: 3px;
font-weight: normal;
}
.memberValueIcon > div {
width: 15px;
}
/******************************************************************************/
/* Read Only Properties */
.memberValueCell.readOnly {
opacity: 0.5;
}
.memberValueIcon.readOnly {
background: url("read-only-prop.svg") no-repeat;
background-position: 4px 4px;
background-size: 10px 10px;
}
/******************************************************************************/
.memberRow.hasChildren > .memberLabelCell > .memberIcon:hover,
.memberRow.cropped > .memberLabelCell > .memberIcon:hover {
cursor: pointer;
}
.memberRow.hasChildren > .memberLabelCell > .memberLabel:hover,
.memberRow.cropped > .memberLabelCell > .memberLabel:hover {
cursor: pointer;
color: blue;
text-decoration: underline;
}
.memberRow:hover {
background-color: #EFEFEF;
}
.memberRow {
padding: 3px 0 3px 0;
}
.panelNode-dom .memberRow td,
.panelNode-domSide .memberRow td {
border-bottom: 1px solid #EFEFEF;
}
/******************************************************************************/
.userLabel,
.userClassLabel,
.userFunctionLabel {
font-weight: bold;
}
.userLabel {
color: #000000;
}
.userClassLabel {
color: #E90000;
}
.userFunctionLabel {
color: #025E2A;
}
.domLabel {
color: #000000;
}
.domClassLabel {
color: #E90000;
}
.domFunctionLabel {
color: #025E2A;
}
.ordinalLabel {
color: SlateBlue;
font-weight: bold;
}
/******************************************************************************/
/* Twisties */
.memberRow > .memberLabelCell > .memberIcon {
height: 14px;
width: 14px;
display: inline-block;
line-height: 15px;
vertical-align: bottom;
padding-right: 2px;
margin-left: 3px;
}
.memberRow.hasChildren > .memberLabelCell > .memberIcon,
.memberRow.cropped > .memberLabelCell > .memberIcon {
background-image: url("./twisty-closed.svg");
background-repeat: no-repeat;
}
.memberRow.hasChildren.opened > .memberLabelCell > .memberIcon,
.memberRow.cropped.opened > .memberLabelCell > .memberIcon {
background-image: url("./twisty-open.svg");
background-repeat: no-repeat;
}
@media (min-resolution: 1.1dppx) {
.memberRow.hasChildren > .memberLabelCell > .memberIcon,
.memberRow.cropped > .memberLabelCell > .memberIcon {
background-image: url("./controls@2x.png");
}
.memberRow.hasChildren.opened > .memberLabelCell > .memberIcon,
.memberRow.cropped.opened > .memberLabelCell > .memberIcon {
background-image: url("./controls@2x.png");
}
}
/******************************************************************************/
/* Layout support */
.memberChildren {
padding-left: 16px;
}
.memberLabelCell,
.memberValueCell {
}
.memberLabelCell {
min-width: 30px;
}
.memberRow:hover {
background-color: transparent !important;
}
/******************************************************************************/
/* Themes */
.theme-light .memberRow.hasChildren > .memberLabelCell > .memberIcon,
.theme-light .memberRow.cropped > .memberLabelCell > .memberIcon {
background-image: url("./controls.png");
background-size: 56px 28px;
background-repeat: no-repeat;
background-position: 0 -14px;
}
.theme-light .memberRow.hasChildren.opened > .memberLabelCell > .memberIcon,
.theme-light .memberRow.cropped.opened > .memberLabelCell > .memberIcon {
background-image: url("./controls.png");
background-size: 56px 28px;
background-repeat: no-repeat;
background-position: -14px -14px;
}
.theme-dark .memberRow.hasChildren > .memberLabelCell > .memberIcon,
.theme-dark .memberRow.cropped > .memberLabelCell > .memberIcon {
background-image: url("./controls.png");
background-size: 56px 28px;
background-repeat: no-repeat;
background-position: -28px -14px;
}
.theme-dark .memberRow.hasChildren.opened > .memberLabelCell > .memberIcon,
.theme-dark .memberRow.cropped.opened > .memberLabelCell > .memberIcon {
background-image: url("./controls.png");
background-size: 56px 28px;
background-repeat: no-repeat;
background-position: -42px -14px;
}
.theme-dark .memberRow:hover {
background-color: var(--theme-selection-background-semitransparent);
}

View File

@ -4,9 +4,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@import "resource://devtools/client/shared/components/reps/reps.css";
@import "resource://devtools/client/shared/components/tree/tree-view.css";
@import "general.css";
@import "dom-tree.css";
@import "search-box.css";
@import "tabs.css";
@import "toolbar.css";
@ -20,6 +20,18 @@
.panelContent {
overflow-y: auto;
font-size: 11px;
font-family: var(--monospace-font-family);
}
/* The tree takes the entire horizontal space within the panel content. */
.panelContent .treeTable {
width: 100%;
}
/* Make sure there is a little space between label and value columns. */
.panelContent .treeTable .treeLabelCell {
padding-right: 17px;
}
/******************************************************************************/

View File

@ -8,7 +8,6 @@
DevToolsModules(
'controls.png',
'controls@2x.png',
'dom-tree.css',
'general.css',
'headers-panel.css',
'json-panel.css',

View File

@ -8,8 +8,7 @@
"use strict";
define(function(require, exports, module) {
// ReactJS
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { render } = require("devtools/client/shared/vendor/react-dom");
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
@ -79,7 +78,7 @@ define(function(require, exports, module) {
* at the top of the window. This component also represents ReacJS root.
*/
let content = document.getElementById("content");
let theApp = ReactDOM.render(MainTabbedArea(input), content);
let theApp = render(MainTabbedArea(input), content);
let onResize = event => {
window.document.body.style.height = window.innerHeight + "px";

View File

@ -12,10 +12,10 @@ add_task(function* () {
yield addJsonViewTab(TEST_JSON_URL);
let countBefore = yield getElementCount(".jsonPanelBox .domTable .memberRow");
let countBefore = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
ok(countBefore == 1, "There must be one row");
let text = yield getElementText(".jsonPanelBox .domTable .memberRow");
let text = yield getElementText(".jsonPanelBox .treeTable .treeRow");
is(text, "name\"value\"", "There must be proper JSON displayed");
// Verify JSON copy into the clipboard.

View File

@ -12,7 +12,7 @@ add_task(function* () {
yield addJsonViewTab(TEST_JSON_URL);
let count = yield getElementCount(".jsonPanelBox .domTable .memberRow");
let count = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
is(count, 3, "There must be three rows");
// XXX use proper shortcut to focus the filter box
@ -23,6 +23,6 @@ add_task(function* () {
yield waitForFilter();
let hiddenCount = yield getElementCount(
".jsonPanelBox .domTable .memberRow.hidden");
".jsonPanelBox .treeTable .treeRow.hidden");
is(hiddenCount, 2, "There must be two hidden rows");
});

View File

@ -12,7 +12,7 @@ add_task(function* () {
yield addJsonViewTab(TEST_JSON_URL);
let count = yield getElementCount(".jsonPanelBox .domTable .memberRow");
let count = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
ok(count == 0, "There must be no row");
let text = yield getElementText(".jsonPanelBox .jsonParseError");

View File

@ -12,11 +12,11 @@ add_task(function* () {
yield addJsonViewTab(TEST_JSON_URL);
let countBefore = yield getElementCount(".jsonPanelBox .domTable .memberRow");
let countBefore = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
ok(countBefore == 1, "There must be one row");
yield expandJsonNode(".jsonPanelBox .domTable .memberLabel");
yield expandJsonNode(".jsonPanelBox .treeTable .treeLabel");
let countAfter = yield getElementCount(".jsonPanelBox .domTable .memberRow");
let countAfter = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
ok(countAfter == 3, "There must be three rows");
});

View File

@ -72,7 +72,7 @@ addMessageListener("Test:JsonView:SendString", function(msg) {
addMessageListener("Test:JsonView:WaitForFilter", function(msg) {
let firstRow = content.document.querySelector(
".jsonPanelBox .domTable .memberRow");
".jsonPanelBox .treeTable .treeRow");
// Check if the filter is already set.
if (firstRow.classList.contains("hidden")) {

View File

@ -120,6 +120,10 @@
- in the network details footer for the "Flash" filtering button. -->
<!ENTITY netmonitorUI.footer.filterFlash "Flash">
<!-- LOCALIZATION NOTE (netmonitorUI.footer.filterWS): This is the label displayed
- in the network details footer for the "WS" filtering button. -->
<!ENTITY netmonitorUI.footer.filterWS "WS">
<!-- LOCALIZATION NOTE (netmonitorUI.footer.filterOther): This is the label displayed
- in the network details footer for the "Other" filtering button. -->
<!ENTITY netmonitorUI.footer.filterOther "Other">

View File

@ -17,3 +17,7 @@ responsive.title=Responsive Design Mode
# LOCALIZATION NOTE (responsive.exit): tooltip text of the exit button.
responsive.exit=Close Responsive Design Mode
# LOCALIZATION NOTE (responsive.noDeviceSelected): placeholder text for the
# device selector
responsive.noDeviceSelected=no device selected

View File

@ -975,7 +975,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
*
* @param string type
* Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
* "flash" or "other".
* "flash", "ws" or "other".
*/
filterOn: function(type = "all") {
if (type === "all") {
@ -1018,7 +1018,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
*
* @param string type
* Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
* "flash" or "other".
* "flash", "ws" or "other".
*/
_disableFilter: function(type) {
// Remove the filter from list of active filters.
@ -1040,7 +1040,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
*
* @param string type
* Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
* "flash" or "other".
* "flash", "ws" or "other".
*/
_enableFilter: function(type) {
// Make sure this is a valid filter type.
@ -1092,6 +1092,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
images: this.isImage,
media: this.isMedia,
flash: this.isFlash,
ws: this.isWS,
other: this.isOther,
freetext: this.isFreetextMatch
};
@ -1232,8 +1233,10 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
mimeType.includes("/x-javascript"));
},
isXHR: function({ attachment: { isXHR } }) {
return isXHR;
isXHR: function(item) {
// Show the request it is XHR, except
// if the request is a WS upgrade
return item.attachment.isXHR && !this.isWS(item);
},
isFont: function({ attachment: { url, mimeType } }) {
@ -1268,6 +1271,38 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
url.includes(".flv");
},
isWS: function({ attachment: { requestHeaders, responseHeaders } }) {
// Detect a websocket upgrade if request has an Upgrade header
// with value 'websocket'
if (!requestHeaders || !Array.isArray(requestHeaders.headers)) {
return false;
}
// Find the 'upgrade' header.
var upgradeHeader = requestHeaders.headers.find(header => {
return (header.name == "Upgrade");
});
// If no header found on request, check response - mainly to get
// something we can unit test, as it is impossible to set
// the Upgrade header on outgoing XHR as per the spec.
if (!upgradeHeader && responseHeaders &&
Array.isArray(responseHeaders.headers)) {
upgradeHeader = responseHeaders.headers.find(header => {
return (header.name == "Upgrade");
});
}
// Return false if there is no such header or if its value isn't
// 'websocket'.
if (!upgradeHeader || upgradeHeader.value != "websocket") {
return false;
}
return true;
},
isOther: function(e) {
return !this.isHtml(e) &&
!this.isCss(e) &&
@ -1276,7 +1311,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
!this.isFont(e) &&
!this.isImage(e) &&
!this.isMedia(e) &&
!this.isFlash(e);
!this.isFlash(e) &&
!this.isWS(e);
},
isFreetextMatch: function({ attachment: { url } }, text) {
@ -3604,7 +3640,8 @@ PerformanceStatisticsView.prototype = {
*/
_sanitizeChartDataSource: function(items, emptyCache) {
let data = [
"html", "css", "js", "xhr", "fonts", "images", "media", "flash", "other"
"html", "css", "js", "xhr", "fonts", "images", "media", "flash", "ws",
"other"
].map(e => ({
cached: 0,
count: 0,
@ -3638,13 +3675,16 @@ PerformanceStatisticsView.prototype = {
} else if (RequestsMenuView.prototype.isFlash(requestItem)) {
// "flash"
type = 7;
} else if (RequestsMenuView.prototype.isWS(requestItem)) {
// "ws"
type = 8;
} else if (RequestsMenuView.prototype.isXHR(requestItem)) {
// Verify XHR last, to categorize other mime types in their own blobs.
// "xhr"
type = 3;
} else {
// "other"
type = 8;
type = 9;
}
if (emptyCache || !responseIsFresh(details)) {

View File

@ -143,6 +143,11 @@
data-key="flash"
label="&netmonitorUI.footer.filterFlash;">
</button>
<button id="requests-menu-filter-ws-button"
class="requests-menu-filter-button"
data-key="ws"
label="&netmonitorUI.footer.filterWS;">
</button>
<button id="requests-menu-filter-other-button"
class="requests-menu-filter-button"
data-key="other"

View File

@ -21,6 +21,11 @@ const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
{ url: "sjs_content-type-test-server.sjs?fmt=flash" },
]);
const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
/* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
]);
function test() {
initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
@ -39,7 +44,7 @@ function test() {
RequestsMenu.lazyUpdate = false;
waitForNetworkEvents(aMonitor, 8).then(() => {
waitForNetworkEvents(aMonitor, 9).then(() => {
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
isnot(RequestsMenu.selectedItem, null,
@ -51,83 +56,89 @@ function test() {
// First test with single filters...
testFilterButtons(aMonitor, "all");
testContents([1, 1, 1, 1, 1, 1, 1, 1])
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1])
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
testFilterButtons(aMonitor, "html");
return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
// Reset filters
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
testFilterButtons(aMonitor, "css");
return testContents([0, 1, 0, 0, 0, 0, 0, 0]);
return testContents([0, 1, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
testFilterButtons(aMonitor, "js");
return testContents([0, 0, 1, 0, 0, 0, 0, 0]);
return testContents([0, 0, 1, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-xhr-button"));
testFilterButtons(aMonitor, "xhr");
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 0]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-fonts-button"));
testFilterButtons(aMonitor, "fonts");
return testContents([0, 0, 0, 1, 0, 0, 0, 0]);
return testContents([0, 0, 0, 1, 0, 0, 0, 0, 0]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-images-button"));
testFilterButtons(aMonitor, "images");
return testContents([0, 0, 0, 0, 1, 0, 0, 0]);
return testContents([0, 0, 0, 0, 1, 0, 0, 0, 0]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-media-button"));
testFilterButtons(aMonitor, "media");
return testContents([0, 0, 0, 0, 0, 1, 1, 0]);
return testContents([0, 0, 0, 0, 0, 1, 1, 0, 0]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
testFilterButtons(aMonitor, "flash");
return testContents([0, 0, 0, 0, 0, 0, 0, 1]);
return testContents([0, 0, 0, 0, 0, 0, 0, 1, 0]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
testFilterButtons(aMonitor, "ws");
return testContents([0, 0, 0, 0, 0, 0, 0, 0, 1]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
testFilterButtons(aMonitor, "all");
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
})
.then(() => {
// Text in filter box that matches nothing should hide all.
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
setFreetextFilter("foobar");
return testContents([0, 0, 0, 0, 0, 0, 0, 0]);
return testContents([0, 0, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
// Text in filter box that matches should filter out everything else.
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
setFreetextFilter("sample");
return testContents([1, 1, 1, 0, 0, 0, 0, 0]);
return testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
// Text in filter box that matches should filter out everything else.
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
setFreetextFilter("SAMPLE");
return testContents([1, 1, 1, 0, 0, 0, 0, 0]);
return testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
// Test negative filtering (only show unmatched items)
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
setFreetextFilter("-sample");
return testContents([0, 0, 0, 1, 1, 1, 1, 1]);
return testContents([0, 0, 0, 1, 1, 1, 1, 1, 1]);
})
// ...then combine multiple filters together.
.then(() => {
@ -135,43 +146,44 @@ function test() {
setFreetextFilter("");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
return testContents([1, 1, 0, 0, 0, 0, 0, 0]);
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
return testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
// Html and css filter enabled and text filter should show just the html and css match.
// Should not show both the items that match the button plus the items that match the text.
setFreetextFilter("sample");
return testContents([1, 1, 0, 0, 0, 0, 0, 0]);
return testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
setFreetextFilter("");
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0]);
return testContents([1, 1, 0, 0, 0, 0, 0, 1]);
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0]);
return testContents([1, 1, 0, 0, 0, 0, 0, 1, 0]);
})
.then(() => {
// Disable some filters. Only one left active.
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
testFilterButtons(aMonitor, "html");
return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
// Disable last active filter. Should toggle to all.
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
testFilterButtons(aMonitor, "all");
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
})
.then(() => {
// Enable few filters and click on all. Only "all" should be checked.
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0]);
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 1]);
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
testFilterButtons(aMonitor, "all");
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
})
.then(() => {
return teardown(aMonitor);
@ -261,11 +273,17 @@ function test() {
type: "x-shockwave-flash",
fullMimeType: "application/x-shockwave-flash"
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(8),
"GET", CONTENT_TYPE_SJS + "?fmt=ws", {
fuzzyUrl: true,
status: 101,
statusText: "Switching Protocols",
});
return promise.resolve(null);
}
loadCommonFrameScript();
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
});
}

View File

@ -21,6 +21,11 @@ const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
{ url: "sjs_content-type-test-server.sjs?fmt=flash" },
]);
const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
/* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
]);
function test() {
initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
@ -33,7 +38,7 @@ function test() {
RequestsMenu.lazyUpdate = false;
waitForNetworkEvents(aMonitor, 8).then(() => {
waitForNetworkEvents(aMonitor, 9).then(() => {
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
isnot(RequestsMenu.selectedItem, null,
@ -44,38 +49,38 @@ function test() {
"The details pane should not be hidden after toggle button was pressed.");
testFilterButtons(aMonitor, "all");
testContents([1, 1, 1, 1, 1, 1, 1, 1])
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1])
.then(() => {
info("Testing html filtering.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
testFilterButtons(aMonitor, "html");
return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
info("Performing more requests.");
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
return waitForNetworkEvents(aMonitor, 8);
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
return waitForNetworkEvents(aMonitor, 9);
})
.then(() => {
info("Testing html filtering again.");
testFilterButtons(aMonitor, "html");
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]);
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
info("Performing more requests.");
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
return waitForNetworkEvents(aMonitor, 8);
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
return waitForNetworkEvents(aMonitor, 9);
})
.then(() => {
info("Testing html filtering again.");
testFilterButtons(aMonitor, "html");
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]);
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
info("Resetting filters.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
testFilterButtons(aMonitor, "all");
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
})
.then(() => {
return teardown(aMonitor);
@ -101,7 +106,7 @@ function test() {
"The item at index " + i + " doesn't have the correct hidden state.");
}
for (let i = 0; i < aVisibility.length; i += 8) {
for (let i = 0; i < aVisibility.length; i += 9) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=html", {
fuzzyUrl: true,
@ -111,7 +116,7 @@ function test() {
fullMimeType: "text/html; charset=utf-8"
});
}
for (let i = 1; i < aVisibility.length; i += 8) {
for (let i = 1; i < aVisibility.length; i += 9) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
fuzzyUrl: true,
@ -121,7 +126,7 @@ function test() {
fullMimeType: "text/css; charset=utf-8"
});
}
for (let i = 2; i < aVisibility.length; i += 8) {
for (let i = 2; i < aVisibility.length; i += 9) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
fuzzyUrl: true,
@ -131,7 +136,7 @@ function test() {
fullMimeType: "application/javascript; charset=utf-8"
});
}
for (let i = 3; i < aVisibility.length; i += 8) {
for (let i = 3; i < aVisibility.length; i += 9) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=font", {
fuzzyUrl: true,
@ -141,7 +146,7 @@ function test() {
fullMimeType: "font/woff"
});
}
for (let i = 4; i < aVisibility.length; i += 8) {
for (let i = 4; i < aVisibility.length; i += 9) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=image", {
fuzzyUrl: true,
@ -151,7 +156,7 @@ function test() {
fullMimeType: "image/png"
});
}
for (let i = 5; i < aVisibility.length; i += 8) {
for (let i = 5; i < aVisibility.length; i += 9) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=audio", {
fuzzyUrl: true,
@ -161,7 +166,7 @@ function test() {
fullMimeType: "audio/ogg"
});
}
for (let i = 6; i < aVisibility.length; i += 8) {
for (let i = 6; i < aVisibility.length; i += 9) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=video", {
fuzzyUrl: true,
@ -171,7 +176,7 @@ function test() {
fullMimeType: "video/webm"
});
}
for (let i = 7; i < aVisibility.length; i += 8) {
for (let i = 7; i < aVisibility.length; i += 9) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=flash", {
fuzzyUrl: true,
@ -181,11 +186,19 @@ function test() {
fullMimeType: "application/x-shockwave-flash"
});
}
for (let i = 8; i < aVisibility.length; i += 9) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=ws", {
fuzzyUrl: true,
status: 101,
statusText: "Switching Protocols"
});
}
return promise.resolve(null);
}
loadCommonFrameScript();
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
});
}

View File

@ -22,6 +22,11 @@ const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
{ url: "sjs_content-type-test-server.sjs?fmt=flash" },
]);
const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
/* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
]);
function test() {
Services.prefs.setCharPref("devtools.netmonitor.filters", '["js", "bogus"]');
@ -40,7 +45,7 @@ function test() {
is(Prefs.filters[1], "bogus",
"The second filter type is invalid, but loaded anyway.");
waitForNetworkEvents(aMonitor, 8).then(() => {
waitForNetworkEvents(aMonitor, 9).then(() => {
testFilterButtons(aMonitor, "js");
ok(true, "Only the correct filter type was taken into consideration.");
@ -54,6 +59,6 @@ function test() {
});
loadCommonFrameScript();
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
});
}

View File

@ -16,6 +16,7 @@ function test() {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-other-button"));
testFilterButtonsCustom(aMonitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1]);
ok(true, "The correct filtering predicates are used before entering perf. analysis mode.");

View File

@ -205,6 +205,14 @@ function handleRequest(request, response) {
response.finish();
break;
}
case "ws": {
response.setStatusLine(request.httpVersion, 101, "Switching Protocols");
response.setHeader("Connection", "upgrade", false);
response.setHeader("Upgrade", "websocket", false);
setCacheHeaders();
response.finish();
break;
}
case "gzip": {
// Note: we're doing a double gzip encoding to test multiple
// converters in network monitor.

View File

@ -260,8 +260,6 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
if (frameName) {
let nameNode = doc.createElement("description");
nameNode.className = "plain call-tree-name";
nameNode.setAttribute("flex", "1");
nameNode.setAttribute("crop", "end");
nameNode.textContent = frameName;
cell.appendChild(nameNode);
}
@ -295,8 +293,6 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
if (frameInfo.fileName) {
let urlNode = doc.createElement("description");
urlNode.className = "plain call-tree-url";
urlNode.setAttribute("flex", "1");
urlNode.setAttribute("crop", "end");
urlNode.textContent = frameInfo.fileName;
urlNode.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + frameInfo.url);
urlNode.addEventListener("mousedown", this._onUrlClick);
@ -324,10 +320,6 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
cell.appendChild(hostNode);
}
let spacerNode = doc.createElement("spacer");
spacerNode.setAttribute("flex", "10000");
cell.appendChild(spacerNode);
if (frameInfo.categoryData.label) {
let categoryNode = doc.createElement("description");
categoryNode.className = "plain call-tree-category";

View File

@ -20,25 +20,31 @@ var PerformanceView = {
states: {
"unavailable": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#unavailable-notice") },
{ sel: "#performance-view-content", opt: "hidden", val: () => true },
],
"empty": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#empty-notice") }
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#empty-notice") },
{ sel: "#performance-view-content", opt: "hidden", val: () => true },
],
"recording": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#recording-notice") }
{ sel: "#performance-view-content", opt: "hidden", val: () => false },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#recording-notice") },
],
"console-recording": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#console-recording-notice") }
{ sel: "#performance-view-content", opt: "hidden", val: () => false },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#console-recording-notice") },
],
"recorded": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#details-pane") }
{ sel: "#performance-view-content", opt: "hidden", val: () => false },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#details-pane") },
],
"loading": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#loading-notice") }
{ sel: "#performance-view-content", opt: "hidden", val: () => false },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#loading-notice") },
]
},

View File

@ -95,7 +95,7 @@
label="&performanceUI.clearButton;"/>
</hbox>
</toolbar>
<vbox id="recordings-list" flex="1"/>
<vbox id="recordings-list" class="theme-sidebar" flex="1"/>
</vbox>
<!-- Main panel content -->
@ -176,8 +176,9 @@
label="&performanceUI.startRecording;"
standalone="true"/>
</hbox>
<label class="tool-disabled-message"
value="&performanceUI.unavailableNoticePB;"/>
<description class="tool-disabled-message">
&performanceUI.unavailableNoticePB;
</description>
</vbox>
<!-- "Empty" notice, shown when there's no recordings available -->

View File

@ -59,7 +59,7 @@ add_task(function() {
let functionCell = D.target.childNodes[5];
is(functionCell.childNodes.length, 8,
is(functionCell.childNodes.length, 7,
"The number of columns displayed for function cells is correct.");
is(functionCell.childNodes[0].className, "arrow theme-twisty",
"The first node displayed for function cells is correct.");
@ -73,8 +73,6 @@ add_task(function() {
"The fifth node displayed for function cells is correct.");
is(functionCell.childNodes[5].className, "plain call-tree-host",
"The sixth node displayed for function cells is correct.");
is(functionCell.childNodes[6].tagName, "spacer",
is(functionCell.childNodes[6].className, "plain call-tree-category",
"The seventh node displayed for function cells is correct.");
is(functionCell.childNodes[7].className, "plain call-tree-category",
"The eight node displayed for function cells is correct.");
});

View File

@ -52,6 +52,7 @@ var RecordingsView = Heritage.extend(WidgetMethods, {
addEmptyRecording: function (recording) {
let titleNode = document.createElement("label");
titleNode.className = "plain recording-item-title";
titleNode.setAttribute("crop", "end");
titleNode.setAttribute("value", recording.getLabel() ||
L10N.getFormatStr("recordingsList.itemLabel", this.itemCount + 1));

View File

@ -0,0 +1,29 @@
/* 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 {
ADD_DEVICE,
ADD_DEVICE_TYPE,
} = require("./index");
module.exports = {
addDevice(device, deviceType) {
return {
type: ADD_DEVICE,
device,
deviceType,
};
},
addDeviceType(deviceType) {
return {
type: ADD_DEVICE_TYPE,
deviceType,
};
},
};

View File

@ -10,13 +10,22 @@
createEnum([
// The location of the page has changed. This may be triggered by the user
// directly entering a new URL, navigating with links, etc.
"CHANGE_LOCATION",
// Add a new device.
"ADD_DEVICE",
// Add a new device type.
"ADD_DEVICE_TYPE",
// Add an additional viewport to display the document.
"ADD_VIEWPORT",
// Change the device displayed in the viewport.
"CHANGE_DEVICE",
// The location of the page has changed. This may be triggered by the user
// directly entering a new URL, navigating with links, etc.
"CHANGE_LOCATION",
// Resize the viewport.
"RESIZE_VIEWPORT",

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'devices.js',
'index.js',
'location.js',
'viewports.js',

View File

@ -6,6 +6,7 @@
const {
ADD_VIEWPORT,
CHANGE_DEVICE,
RESIZE_VIEWPORT,
ROTATE_VIEWPORT
} = require("./index");
@ -21,6 +22,17 @@ module.exports = {
};
},
/**
* Change the viewport device.
*/
changeDevice(id, device) {
return {
type: CHANGE_DEVICE,
id,
device,
};
},
/**
* Resize the viewport.
*/

View File

@ -8,7 +8,11 @@ const { createClass, createFactory, PropTypes, DOM: dom } =
require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { resizeViewport, rotateViewport } = require("./actions/viewports");
const {
changeDevice,
resizeViewport,
rotateViewport
} = require("./actions/viewports");
const Types = require("./types");
const Viewports = createFactory(require("./components/viewports"));
const GlobalToolbar = createFactory(require("./components/global-toolbar"));
@ -18,29 +22,36 @@ let App = createClass({
displayName: "App",
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
location: Types.location.isRequired,
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
onExit: PropTypes.func.isRequired,
},
onRotateViewport(id) {
this.props.dispatch(rotateViewport(id));
onChangeViewportDevice(id, device) {
this.props.dispatch(changeDevice(id, device));
},
onResizeViewport(id, width, height) {
this.props.dispatch(resizeViewport(id, width, height));
},
onRotateViewport(id) {
this.props.dispatch(rotateViewport(id));
},
render() {
let {
devices,
location,
viewports,
onExit,
} = this.props;
let {
onRotateViewport,
onChangeViewportDevice,
onResizeViewport,
onRotateViewport,
} = this;
return dom.div(
@ -51,8 +62,10 @@ let App = createClass({
onExit,
}),
Viewports({
devices,
location,
viewports,
onChangeViewportDevice,
onRotateViewport,
onResizeViewport,
})

View File

@ -0,0 +1,83 @@
/* 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 { getStr } = require("./utils/l10n");
const { DOM: dom, createClass, PropTypes, addons } =
require("devtools/client/shared/vendor/react");
const Types = require("../types");
module.exports = createClass({
displayName: "DeviceSelector",
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
selectedDevice: PropTypes.string.isRequired,
onChangeViewportDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
onSelectChange({ target }) {
let {
devices,
onChangeViewportDevice,
onResizeViewport,
} = this.props;
for (let type of devices.types) {
for (let device of devices[type]) {
if (device.name === target.value) {
onResizeViewport(device.width, device.height);
break;
}
}
}
onChangeViewportDevice(target.value);
},
render() {
let {
devices,
selectedDevice,
} = this.props;
let options = [];
for (let type of devices.types) {
for (let device of devices[type]) {
options.push(device);
}
}
let selectClass = "viewport-device-selector";
if (selectedDevice) {
selectClass += " selected";
}
return dom.select(
{
className: selectClass,
value: selectedDevice,
onChange: this.onSelectChange,
},
dom.option({
value: "",
disabled: true,
hidden: true,
}, getStr("responsive.noDeviceSelected")),
options.map(device => {
return dom.option({
key: device.name,
value: device.name,
}, device.name);
})
);
},
});

View File

@ -10,6 +10,7 @@ DIRS += [
DevToolsModules(
'browser.js',
'device-selector.js',
'global-toolbar.js',
'resizable-viewport.js',
'viewport-dimension.js',

View File

@ -22,8 +22,10 @@ module.exports = createClass({
displayName: "ResizableViewport",
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
location: Types.location.isRequired,
viewport: PropTypes.shape(Types.viewport).isRequired,
onChangeViewportDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
},
@ -97,6 +99,8 @@ module.exports = createClass({
// Update the viewport store with the new width and height.
this.props.onResizeViewport(width, height);
// Change the device selector back to an unselected device
this.props.onChangeViewportDevice("");
this.setState({
lastClientX,
@ -106,8 +110,11 @@ module.exports = createClass({
render() {
let {
devices,
location,
viewport,
onChangeViewportDevice,
onResizeViewport,
onRotateViewport,
} = this.props;
@ -116,6 +123,10 @@ module.exports = createClass({
className: "resizable-viewport",
},
ViewportToolbar({
devices,
selectedDevice: viewport.device,
onChangeViewportDevice,
onResizeViewport,
onRotateViewport,
}),
Browser({

View File

@ -16,6 +16,7 @@ module.exports = createClass({
propTypes: {
viewport: PropTypes.shape(Types.viewport).isRequired,
onChangeViewportDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
},
@ -54,7 +55,11 @@ module.exports = createClass({
},
onInputBlur() {
this.onInputSubmit();
let { width, height } = this.props.viewport;
if (this.state.width != width || this.state.height != height) {
this.onInputSubmit();
}
this.setState({
isEditing: false,
@ -109,6 +114,8 @@ module.exports = createClass({
return;
}
// Change the device selector back to an unselected device
this.props.onChangeViewportDevice("");
this.props.onResizeViewport(parseInt(this.state.width, 10),
parseInt(this.state.height, 10));
},

View File

@ -4,9 +4,12 @@
"use strict";
const { DOM: dom, createClass, PropTypes, addons } =
const { DOM: dom, createClass, createFactory, PropTypes, addons } =
require("devtools/client/shared/vendor/react");
const Types = require("../types");
const DeviceSelector = createFactory(require("./device-selector"));
module.exports = createClass({
displayName: "ViewportToolbar",
@ -14,11 +17,19 @@ module.exports = createClass({
mixins: [ addons.PureRenderMixin ],
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
selectedDevice: PropTypes.string.isRequired,
onChangeViewportDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
},
render() {
let {
devices,
selectedDevice,
onChangeViewportDevice,
onResizeViewport,
onRotateViewport,
} = this.props;
@ -26,6 +37,12 @@ module.exports = createClass({
{
className: "viewport-toolbar",
},
DeviceSelector({
devices,
selectedDevice,
onChangeViewportDevice,
onResizeViewport,
}),
dom.button({
className: "viewport-rotate-button toolbar-button devtools-button",
onClick: onRotateViewport,

View File

@ -16,12 +16,23 @@ module.exports = createClass({
displayName: "Viewport",
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
location: Types.location.isRequired,
viewport: PropTypes.shape(Types.viewport).isRequired,
onChangeViewportDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
},
onChangeViewportDevice(device) {
let {
viewport,
onChangeViewportDevice,
} = this.props;
onChangeViewportDevice(viewport.id, device);
},
onResizeViewport(width, height) {
let {
viewport,
@ -42,11 +53,13 @@ module.exports = createClass({
render() {
let {
devices,
location,
viewport,
} = this.props;
let {
onChangeViewportDevice,
onRotateViewport,
onResizeViewport,
} = this;
@ -56,13 +69,16 @@ module.exports = createClass({
className: "viewport",
},
ResizableViewport({
devices,
location,
viewport,
onChangeViewportDevice,
onResizeViewport,
onRotateViewport,
}),
ViewportDimension({
viewport,
onChangeViewportDevice,
onResizeViewport,
})
);

View File

@ -15,16 +15,20 @@ module.exports = createClass({
displayName: "Viewports",
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
location: Types.location.isRequired,
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
onChangeViewportDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
},
render() {
let {
devices,
location,
viewports,
onChangeViewportDevice,
onResizeViewport,
onRotateViewport,
} = this.props;
@ -36,8 +40,10 @@ module.exports = createClass({
viewports.map(viewport => {
return Viewport({
key: viewport.id,
devices,
location,
viewport,
onChangeViewportDevice,
onResizeViewport,
onRotateViewport,
});

View File

@ -8,4 +8,5 @@ DevToolsModules(
'close.svg',
'grippers.svg',
'rotate-viewport.svg',
'select-arrow.svg',
)

View File

@ -0,0 +1,29 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
<defs>
<style>
use:not(:target) {
display: none;
}
#light {
fill: #dde1e4; /* --theme-splitter-color */
}
#light-selected {
fill: #393f4c; /* --theme-body-color */
}
#dark {
fill: #8fa1b2; /* --theme-body-color */
}
#dark-selected {
fill: #f5f7fa; /* --theme-selection-color */
}
</style>
<path id="base-path" d="M7.9 16.3c-.3 0-.6-.1-.8-.4l-4-4.8c-.2-.3-.3-.5-.1-.8.1-.3.5-.3.9-.3h8c.4 0 .7 0 .9.3.2.4.1.6-.1.9l-4 4.8c-.2.3-.5.3-.8.3zM7.8 0c.3 0 .6.1.7.4L12.4 5c.2.3.3.4.1.7-.1.4-.5.3-.8.3H3.9c-.4 0-.8.1-.9-.2-.2-.4-.1-.6.1-.9L7 .3c.2-.3.5-.3.8-.3z"/>
</defs>
<use xlink:href="#base-path" id="light"/>
<use xlink:href="#base-path" id="light-selected"/>
<use xlink:href="#base-path" id="dark"/>
<use xlink:href="#base-path" id="dark-selected"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -7,14 +7,20 @@
.theme-light {
--box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
--viewport-dimension-color: var(--theme-splitter-color);
--viewport-dimension-editing-color: var(--theme-body-color);
--viewport-color: var(--theme-splitter-color);
--viewport-active-color: var(--theme-body-color);
--viewport-selection-arrow: url("./images/select-arrow.svg#light");
--viewport-selection-arrow-selected:
url("./images/select-arrow.svg#light-selected");
}
.theme-dark {
--box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26);
--viewport-dimension-color: var(--theme-body-color);
--viewport-dimension-editing-color: var(--theme-selection-color);
--viewport-color: var(--theme-body-color);
--viewport-active-color: var(--theme-selection-color);
--viewport-selection-arrow: url("./images/select-arrow.svg#dark");
--viewport-selection-arrow-selected:
url("./images/select-arrow.svg#dark-selected");
}
* {
@ -135,10 +141,50 @@ body {
color: var(--theme-body-color);
display: flex;
flex-direction: row;
justify-content: flex-end;
justify-content: center;
height: 18px;
}
.viewport-device-selector {
-moz-appearance: none;
background-color: var(--theme-toolbar-background);
background-image: var(--viewport-selection-arrow);
background-position: 136px;
background-repeat: no-repeat;
background-size: 7px;
border: none;
color: var(--viewport-color);
height: 100%;
padding: 0 16px;
text-align: center;
text-overflow: ellipsis;
width: 150px;
}
.viewport-device-selector.selected {
background-image: var(--viewport-selection-arrow-selected);
color: var(--viewport-active-color);
}
.viewport-device-selector:focus {
background-image: var(--viewport-selection-arrow-selected);
/* Remove the outline from the select box */
color: transparent;
text-shadow: 0 0 0 var(--viewport-active-color);
}
.viewport-device-selector > option {
background-color: var(--theme-toolbar-background);
color: var(--viewport-active-color);
text-align: left;
padding: 5px;
}
.viewport-rotate-button {
position: absolute;
right: 0;
}
.viewport-rotate-button::before {
background-image: url("./images/rotate-viewport.svg");
}
@ -209,13 +255,13 @@ body {
.viewport-dimension-editable,
.viewport-dimension-input {
color: var(--viewport-dimension-color);
color: var(--viewport-color);
transition: all 0.25s ease;
}
.viewport-dimension-editable.editing,
.viewport-dimension-input.editing {
color: var(--viewport-dimension-editing-color);
color: var(--viewport-active-color);
}
.viewport-dimension-editable.editing {

View File

@ -13,6 +13,7 @@ const { require } = BrowserLoader({
baseURI: "resource://devtools/client/responsive.html/",
window: this
});
const { GetDevices } = require("devtools/client/shared/devices");
const Telemetry = require("devtools/client/shared/telemetry");
const { createFactory, createElement } =
@ -22,8 +23,10 @@ const { Provider } = require("devtools/client/shared/vendor/react-redux");
const App = createFactory(require("./app"));
const Store = require("./store");
const { addDevice, addDeviceType } = require("./actions/devices");
const { changeLocation } = require("./actions/location");
const { addViewport } = require("./actions/viewports");
const { loadSheet } = require("sdk/stylesheet/utils");
let bootstrap = {
@ -32,6 +35,11 @@ let bootstrap = {
store: null,
init() {
// Load a special UA stylesheet to reset certain styles such as dropdown
// lists.
loadSheet(window,
"resource://devtools/client/responsive.html/responsive-ua.css",
"agent");
this.telemetry.toolOpened("responsive");
let store = this.store = Store();
let app = App({
@ -83,6 +91,18 @@ Object.defineProperty(window, "store", {
window.addInitialViewport = contentURI => {
try {
bootstrap.dispatch(changeLocation(contentURI));
GetDevices().then(devices => {
for (let type of devices.TYPES) {
bootstrap.dispatch(addDeviceType(type));
for (let device of devices[type]) {
if (device.os != "fxos") {
bootstrap.dispatch(addDevice(device, type));
}
}
}
});
bootstrap.dispatch(addViewport());
} catch (e) {
console.error(e);

View File

@ -17,6 +17,7 @@ DevToolsModules(
'index.css',
'manager.js',
'reducers.js',
'responsive-ua.css',
'store.js',
'types.js',
)

View File

@ -4,5 +4,6 @@
"use strict";
exports.devices = require("./reducers/devices");
exports.location = require("./reducers/location");
exports.viewports = require("./reducers/viewports");

View File

@ -0,0 +1,39 @@
/* 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 {
ADD_DEVICE,
ADD_DEVICE_TYPE,
} = require("../actions/index");
const INITIAL_DEVICES = {
types: [],
};
let reducers = {
[ADD_DEVICE](devices, { device, deviceType }) {
return Object.assign({}, devices, {
[deviceType]: [...devices[deviceType], device],
});
},
[ADD_DEVICE_TYPE](devices, { deviceType }) {
return Object.assign({}, devices, {
types: [...devices.types, deviceType],
[deviceType]: [],
});
},
};
module.exports = function(devices = INITIAL_DEVICES, action) {
let reducer = reducers[action.type];
if (!reducer) {
return devices;
}
return reducer(devices, action);
};

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'devices.js',
'location.js',
'viewports.js',
)

View File

@ -6,6 +6,7 @@
const {
ADD_VIEWPORT,
CHANGE_DEVICE,
RESIZE_VIEWPORT,
ROTATE_VIEWPORT,
} = require("../actions/index");
@ -15,6 +16,7 @@ let nextViewportId = 0;
const INITIAL_VIEWPORTS = [];
const INITIAL_VIEWPORT = {
id: nextViewportId++,
device: "",
width: 320,
height: 480,
};
@ -29,6 +31,18 @@ let reducers = {
return [...viewports, Object.assign({}, INITIAL_VIEWPORT)];
},
[CHANGE_DEVICE](viewports, { id, device }) {
return viewports.map(viewport => {
if (viewport.id !== id) {
return viewport;
}
return Object.assign({}, viewport, {
device,
});
});
},
[RESIZE_VIEWPORT](viewports, { id, width, height }) {
return viewports.map(viewport => {
if (viewport.id !== id) {

View File

@ -0,0 +1,6 @@
@namespace url(http://www.w3.org/1999/xhtml);
/* Reset default UA styles for dropdown options */
*|*::-moz-dropdown-list {
border: 0 !important;
}

View File

@ -2,6 +2,7 @@
tags = devtools
subsuite = devtools
support-files =
browser_devices.json
head.js
[browser_exit_button.js]

View File

@ -0,0 +1,25 @@
{
"TYPES": [ "phones" ],
"phones": [
{
"name": "Firefox OS Flame",
"width": 320,
"height": 570,
"pixelRatio": 1.5,
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
"touch": true,
"firefoxOS": true,
"os": "fxos"
},
{
"name": "Alcatel One Touch Fire",
"width": 320,
"height": 480,
"pixelRatio": 1,
"userAgent": "Mozilla/5.0 (Mobile; ALCATELOneTouch4012X; rv:28.0) Gecko/28.0 Firefox/28.0",
"touch": true,
"firefoxOS": true,
"os": "fxos"
},
],
}

View File

@ -14,8 +14,16 @@ Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-redux-head.js",
this);
const TEST_URI_ROOT = "http://example.com/browser/devtools/client/responsive.html/test/browser/";
DevToolsUtils.testing = true;
Services.prefs.setCharPref("devtools.devices.url",
TEST_URI_ROOT + "browser_devices.json");
Services.prefs.setBoolPref("devtools.responsive.html.enabled", true);
registerCleanupFunction(() => {
DevToolsUtils.testing = false;
Services.prefs.clearUserPref("devtools.devices.url");
Services.prefs.clearUserPref("devtools.responsive.html.enabled");
});
const { ResponsiveUIManager } = Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {});

View File

@ -0,0 +1,35 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test adding a new device.
const {
addDevice,
addDeviceType,
} = require("devtools/client/responsive.html/actions/devices");
add_task(function*() {
let store = Store();
const { getState, dispatch } = store;
let device = {
"name": "Firefox OS Flame",
"width": 320,
"height": 570,
"pixelRatio": 1.5,
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
"touch": true,
"firefoxOS": true,
"os": "fxos"
};
dispatch(addDeviceType("phones"));
dispatch(addDevice(device, "phones"));
equal(getState().devices.phones.length, 1,
"Correct number of phones");
ok(getState().devices.phones.includes(device),
"Device phone list contains Firefox OS Flame");
});

View File

@ -0,0 +1,22 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test adding a new device type.
const { addDeviceType } =
require("devtools/client/responsive.html/actions/devices");
add_task(function*() {
let store = Store();
const { getState, dispatch } = store;
dispatch(addDeviceType("phones"));
equal(getState().devices.types.length, 1, "Correct number of device types");
equal(getState().devices.phones.length, 0,
"Defaults to an empty array of phones");
ok(getState().devices.types.includes("phones"),
"Device types contain phones");
});

View File

@ -0,0 +1,42 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test changing the viewport device.
const {
addDevice,
addDeviceType,
} = require("devtools/client/responsive.html/actions/devices");
const {
addViewport,
changeDevice,
} = require("devtools/client/responsive.html/actions/viewports");
add_task(function*() {
let store = Store();
const { getState, dispatch } = store;
dispatch(addDeviceType("phones"));
dispatch(addDevice({
"name": "Firefox OS Flame",
"width": 320,
"height": 570,
"pixelRatio": 1.5,
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
"touch": true,
"firefoxOS": true,
"os": "fxos"
}, "phones"));
dispatch(addViewport());
let viewport = getState().viewports[0];
equal(viewport.device, "", "Default device is unselected");
dispatch(changeDevice(0, "Firefox OS Flame"));
viewport = getState().viewports[0];
equal(viewport.device, "Firefox OS Flame",
"Changed to Firefox OS Flame device");
});

View File

@ -4,7 +4,10 @@ head = head.js ../../../framework/test/shared-redux-head.js
tail =
firefox-appdir = browser
[test_add_device.js]
[test_add_device_type.js]
[test_add_viewport.js]
[test_change_location.js]
[test_change_viewport_device.js]
[test_resize_viewport.js]
[test_rotate_viewport.js]

View File

@ -9,6 +9,67 @@ const { PropTypes } = require("devtools/client/shared/vendor/react");
// React PropTypes are used to describe the expected "shape" of various common
// objects that get passed down as props to components.
/**
* A single device that can be displayed in the viewport.
*/
const device = {
// The name of the device
name: PropTypes.string,
// The width of the device
width: PropTypes.number,
// The height of the device
height: PropTypes.number,
// The pixel ratio of the device
pixelRatio: PropTypes.number,
// The user agent string of the device
userAgent: PropTypes.string,
// Whether or not it is a touch device
touch: PropTypes.bool,
// The operating system of the device
os: PropTypes.String,
};
/**
* A list of devices and their types that can be displayed in the viewport.
*/
exports.devices = {
// An array of device types
types: PropTypes.arrayOf(PropTypes.string),
// An array of phone devices
phones: PropTypes.arrayOf(PropTypes.shape(device)),
// An array of tablet devices
tablets: PropTypes.arrayOf(PropTypes.shape(device)),
// An array of laptop devices
laptops: PropTypes.arrayOf(PropTypes.shape(device)),
// An array of television devices
televisions: PropTypes.arrayOf(PropTypes.shape(device)),
// An array of console devices
consoles: PropTypes.arrayOf(PropTypes.shape(device)),
// An array of watch devices
watches: PropTypes.arrayOf(PropTypes.shape(device)),
};
/**
* The location of the document displayed in the viewport(s).
*/
exports.location = PropTypes.string;
/**
* A single viewport displaying a document.
*/
@ -17,6 +78,9 @@ exports.viewport = {
// The id of the viewport
id: PropTypes.number.isRequired,
// The currently selected device applied to the viewport.
device: PropTypes.string,
// The width of the viewport
width: PropTypes.number,
@ -24,8 +88,3 @@ exports.viewport = {
height: PropTypes.number,
};
/**
* The location of the document displayed in the viewport(s).
*/
exports.location = PropTypes.string;

View File

@ -16,24 +16,11 @@
}
.objectBox-object {
font-family: Lucida Grande, sans-serif;
font-weight: bold;
color: DarkGreen;
white-space: pre-wrap;
}
.objectBox-string,
.objectBox-text,
.objectBox-number,
.objectLink-element,
.objectLink-textNode,
.objectLink-function,
.objectBox-stackTrace,
.objectLink-profile,
.objectBox-table {
font-family: monospace;
}
.objectBox-string,
.objectBox-text,
.objectLink-textNode,
@ -79,7 +66,6 @@
right: 4px;
top: 2px;
padding-left: 8px;
font-family: Lucida Grande, sans-serif;
font-weight: bold;
color: #0000FF;
}
@ -96,7 +82,6 @@
.objectLink-regexp,
.objectLink-object,
.objectLink-Date {
font-family: Lucida Grande, sans-serif;
font-weight: bold;
color: DarkGreen;
white-space: pre-wrap;
@ -120,16 +105,6 @@
/******************************************************************************/
.objectLeftBrace,
.objectRightBrace,
.objectEqual,
.objectComma,
.arrayLeftBracket,
.arrayRightBracket,
.arrayComma {
font-family: monospace;
}
.objectLeftBrace,
.objectRightBrace,
.arrayLeftBracket,
@ -152,7 +127,6 @@
/* Cycle reference*/
.objectLink-Reference {
font-family: monospace;
font-weight: bold;
color: rgb(102, 102, 255);
}
@ -165,7 +139,6 @@
/******************************************************************************/
.caption {
font-family: Lucida Grande, Tahoma, sans-serif;
font-weight: bold;
color: #444444;
}
@ -205,7 +178,6 @@
.theme-dark .objectBox-object,
.theme-light .objectBox-object {
font-family: Lucida Grande, sans-serif;
font-weight: normal;
color: var(--theme-highlight-blue);
white-space: pre-wrap;
@ -213,7 +185,6 @@
.theme-dark .caption,
.theme-light .caption {
font-family: Lucida Grande, Tahoma, sans-serif;
font-weight: normal;
color: var(--theme-highlight-blue);
}

View File

@ -5,48 +5,51 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// ReactJS
const React = require("devtools/client/shared/vendor/react");
// Make this available to both AMD and CJS environments
define(function(require, exports, module) {
// ReactJS
const React = require("devtools/client/shared/vendor/react");
// Shortcuts
const { td, span } = React.DOM;
const PropTypes = React.PropTypes;
// Shortcuts
const { td, span } = React.DOM;
const PropTypes = React.PropTypes;
/**
* Render the default cell used for toggle buttons
*/
var LabelCell = React.createClass({
// See the TreeView component for details related
// to the 'member' object.
propTypes: {
member: PropTypes.object.isRequired
},
/**
* Render the default cell used for toggle buttons
*/
let LabelCell = React.createClass({
// See the TreeView component for details related
// to the 'member' object.
propTypes: {
member: PropTypes.object.isRequired
},
displayName: "LabelCell",
displayName: "LabelCell",
render: function() {
let member = this.props.member;
let level = member.level || 0;
render: function() {
let member = this.props.member;
let level = member.level || 0;
// Compute indentation dynamically. The deeper the item is
// inside the hierarchy, the bigger is the left padding.
let rowStyle = {
"paddingLeft": (level * 16) + "px",
};
// Compute indentation dynamically. The deeper the item is
// inside the hierarchy, the bigger is the left padding.
let rowStyle = {
"paddingLeft": (level * 16) + "px",
};
return (
td({
className: "treeLabelCell",
key: "default",
style: rowStyle},
span({ className: "treeIcon" }),
span({ className: "treeLabel " + member.type + "Label" },
member.name
return (
td({
className: "treeLabelCell",
key: "default",
style: rowStyle},
span({ className: "treeIcon" }),
span({ className: "treeLabel " + member.type + "Label" },
member.name
)
)
)
);
}
});
);
}
});
// Exports from this module
module.exports = LabelCell;
// Exports from this module
module.exports = LabelCell;
});

Some files were not shown because too many files have changed in this diff Show More