mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-24 05:44:10 +00:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
53f935b036
@ -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');
|
||||
})();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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 -)
|
||||
|
@ -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"'
|
||||
|
@ -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 = {
|
||||
|
@ -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="©Cmd.label;"
|
||||
accesskey="©Cmd.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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
Binary file not shown.
@ -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?
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
|
||||
|
@ -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`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
14
build.gradle
14
build.gradle
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
47
devtools/client/aboutdebugging/components/addon-target.js
Normal file
47
devtools/client/aboutdebugging/components/addon-target.js
Normal 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 });
|
||||
},
|
||||
});
|
@ -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");
|
||||
|
||||
|
@ -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 });
|
||||
});
|
||||
},
|
||||
|
@ -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',
|
||||
)
|
||||
|
@ -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"))
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -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;
|
||||
},
|
||||
});
|
@ -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:
|
||||
|
@ -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(() => {
|
||||
|
@ -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",
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
])
|
||||
},
|
||||
|
||||
|
@ -7,5 +7,4 @@
|
||||
DevToolsModules(
|
||||
'tabs.js',
|
||||
'toolbar.js',
|
||||
'tree-view.js',
|
||||
)
|
||||
|
@ -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;
|
||||
});
|
@ -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})
|
||||
);
|
||||
|
@ -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",
|
||||
|
@ -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>" +
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -8,7 +8,6 @@
|
||||
DevToolsModules(
|
||||
'controls.png',
|
||||
'controls@2x.png',
|
||||
'dom-tree.css',
|
||||
'general.css',
|
||||
'headers-panel.css',
|
||||
'json-panel.css',
|
||||
|
@ -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";
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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")) {
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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)) {
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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.");
|
||||
|
@ -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.
|
||||
|
@ -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";
|
||||
|
@ -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") },
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -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 -->
|
||||
|
@ -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.");
|
||||
});
|
||||
|
@ -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));
|
||||
|
||||
|
29
devtools/client/responsive.html/actions/devices.js
Normal file
29
devtools/client/responsive.html/actions/devices.js
Normal 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,
|
||||
};
|
||||
},
|
||||
|
||||
};
|
@ -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",
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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);
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
});
|
@ -10,6 +10,7 @@ DIRS += [
|
||||
|
||||
DevToolsModules(
|
||||
'browser.js',
|
||||
'device-selector.js',
|
||||
'global-toolbar.js',
|
||||
'resizable-viewport.js',
|
||||
'viewport-dimension.js',
|
||||
|
@ -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({
|
||||
|
@ -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));
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
})
|
||||
);
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -8,4 +8,5 @@ DevToolsModules(
|
||||
'close.svg',
|
||||
'grippers.svg',
|
||||
'rotate-viewport.svg',
|
||||
'select-arrow.svg',
|
||||
)
|
||||
|
29
devtools/client/responsive.html/images/select-arrow.svg
Normal file
29
devtools/client/responsive.html/images/select-arrow.svg
Normal 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 |
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -17,6 +17,7 @@ DevToolsModules(
|
||||
'index.css',
|
||||
'manager.js',
|
||||
'reducers.js',
|
||||
'responsive-ua.css',
|
||||
'store.js',
|
||||
'types.js',
|
||||
)
|
||||
|
@ -4,5 +4,6 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
exports.devices = require("./reducers/devices");
|
||||
exports.location = require("./reducers/location");
|
||||
exports.viewports = require("./reducers/viewports");
|
||||
|
39
devtools/client/responsive.html/reducers/devices.js
Normal file
39
devtools/client/responsive.html/reducers/devices.js
Normal 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);
|
||||
};
|
@ -5,6 +5,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'devices.js',
|
||||
'location.js',
|
||||
'viewports.js',
|
||||
)
|
||||
|
@ -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) {
|
||||
|
6
devtools/client/responsive.html/responsive-ua.css
Normal file
6
devtools/client/responsive.html/responsive-ua.css
Normal 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;
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
browser_devices.json
|
||||
head.js
|
||||
|
||||
[browser_exit_button.js]
|
||||
|
@ -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"
|
||||
},
|
||||
],
|
||||
}
|
@ -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", {});
|
||||
|
35
devtools/client/responsive.html/test/unit/test_add_device.js
Normal file
35
devtools/client/responsive.html/test/unit/test_add_device.js
Normal 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");
|
||||
});
|
@ -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");
|
||||
});
|
@ -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");
|
||||
});
|
@ -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]
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user