mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 07:15:46 +00:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
cf4d06f3b0
@ -9,6 +9,8 @@ var StarUI = {
|
||||
_itemId: -1,
|
||||
uri: null,
|
||||
_batching: false,
|
||||
_isNewBookmark: false,
|
||||
_autoCloseTimer: 0,
|
||||
|
||||
_element: function(aID) {
|
||||
return document.getElementById(aID);
|
||||
@ -21,8 +23,11 @@ var StarUI = {
|
||||
// initially the panel is hidden
|
||||
// to avoid impacting startup / new window performance
|
||||
element.hidden = false;
|
||||
element.addEventListener("popuphidden", this, false);
|
||||
element.addEventListener("keypress", this, false);
|
||||
element.addEventListener("mouseout", this, false);
|
||||
element.addEventListener("mouseover", this, false);
|
||||
element.addEventListener("popuphidden", this, false);
|
||||
element.addEventListener("popupshown", this, false);
|
||||
return this.panel = element;
|
||||
},
|
||||
|
||||
@ -58,7 +63,11 @@ var StarUI = {
|
||||
// nsIDOMEventListener
|
||||
handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "mouseover":
|
||||
clearTimeout(this._autoCloseTimer);
|
||||
break;
|
||||
case "popuphidden":
|
||||
clearTimeout(this._autoCloseTimer);
|
||||
if (aEvent.originalTarget == this.panel) {
|
||||
if (!this._element("editBookmarkPanelContent").hidden)
|
||||
this.quitEditMode();
|
||||
@ -72,44 +81,42 @@ var StarUI = {
|
||||
if (this._batching)
|
||||
this.endBatch();
|
||||
|
||||
switch (this._actionOnHide) {
|
||||
case "cancel": {
|
||||
if (!PlacesUIUtils.useAsyncTransactions) {
|
||||
if (this._uriForRemoval) {
|
||||
if (this._isNewBookmark) {
|
||||
if (!PlacesUtils.useAsyncTransactions) {
|
||||
PlacesUtils.transactionManager.undoTransaction();
|
||||
break;
|
||||
}
|
||||
PlacesTransactions.undo().catch(Cu.reportError);
|
||||
PlacesTransactions().undo().catch(Cu.reportError);
|
||||
break;
|
||||
}
|
||||
case "remove": {
|
||||
// Remove all bookmarks for the bookmark's url, this also removes
|
||||
// the tags for the url.
|
||||
if (!PlacesUIUtils.useAsyncTransactions) {
|
||||
let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
|
||||
for (let itemId of itemIds) {
|
||||
let txn = new PlacesRemoveItemTransaction(itemId);
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
}
|
||||
break;
|
||||
// Remove all bookmarks for the bookmark's url, this also removes
|
||||
// the tags for the url.
|
||||
if (!PlacesUIUtils.useAsyncTransactions) {
|
||||
let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
|
||||
for (let itemId of itemIds) {
|
||||
let txn = new PlacesRemoveItemTransaction(itemId);
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
}
|
||||
|
||||
PlacesTransactions.RemoveBookmarksForUrls(this._uriForRemoval)
|
||||
.transact().catch(Cu.reportError);
|
||||
break;
|
||||
}
|
||||
|
||||
PlacesTransactions.RemoveBookmarksForUrls([this._uriForRemoval])
|
||||
.transact().catch(Cu.reportError);
|
||||
}
|
||||
this._actionOnHide = "";
|
||||
}
|
||||
break;
|
||||
case "keypress":
|
||||
clearTimeout(this._autoCloseTimer);
|
||||
|
||||
if (aEvent.defaultPrevented) {
|
||||
// The event has already been consumed inside of the panel.
|
||||
break;
|
||||
}
|
||||
|
||||
switch (aEvent.keyCode) {
|
||||
case KeyEvent.DOM_VK_ESCAPE:
|
||||
if (!this._element("editBookmarkPanelContent").hidden)
|
||||
this.cancelButtonOnCommand();
|
||||
this.panel.hidePopup();
|
||||
break;
|
||||
case KeyEvent.DOM_VK_RETURN:
|
||||
if (aEvent.target.classList.contains("expander-up") ||
|
||||
@ -123,12 +130,40 @@ var StarUI = {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "mouseout": {
|
||||
// Don't handle events for descendent elements.
|
||||
if (aEvent.target != aEvent.currentTarget) {
|
||||
break;
|
||||
}
|
||||
// Explicit fall-through
|
||||
}
|
||||
case "popupshown":
|
||||
// auto-close if new and not interacted with
|
||||
if (this._isNewBookmark) {
|
||||
// 3500ms matches the timeout that Pocket uses in
|
||||
// browser/extensions/pocket/content/panels/js/saved.js
|
||||
let delay = 3500;
|
||||
if (this._closePanelQuickForTesting) {
|
||||
delay /= 10;
|
||||
}
|
||||
this._autoCloseTimer = setTimeout(() => this.panel.hidePopup(), delay, this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_overlayLoaded: false,
|
||||
_overlayLoading: false,
|
||||
showEditBookmarkPopup: Task.async(function* (aNode, aAnchorElement, aPosition) {
|
||||
showEditBookmarkPopup: Task.async(function* (aNode, aAnchorElement, aPosition, aIsNewBookmark) {
|
||||
// Slow double-clicks (not true double-clicks) shouldn't
|
||||
// cause the panel to flicker.
|
||||
if (this.panel.state == "showing" ||
|
||||
this.panel.state == "open") {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isNewBookmark = aIsNewBookmark;
|
||||
this._uriForRemoval = "";
|
||||
// TODO: Deprecate this once async transactions are enabled and the legacy
|
||||
// transactions code is gone (bug 1131491) - we don't want addons to to use
|
||||
// the completeNodeLikeObjectForItemId, so it's better if they keep passing
|
||||
@ -177,26 +212,18 @@ var StarUI = {
|
||||
if (this.panel.state != "closed")
|
||||
return;
|
||||
|
||||
this._blockCommands(); // un-done in the popuphiding handler
|
||||
this._blockCommands(); // un-done in the popuphidden handler
|
||||
|
||||
// Set panel title:
|
||||
// if we are batching, i.e. the bookmark has been added now,
|
||||
// then show Page Bookmarked, else if the bookmark did already exist,
|
||||
// we are about editing it, then use Edit This Bookmark.
|
||||
this._element("editBookmarkPanelTitle").value =
|
||||
this._batching ?
|
||||
this._isNewBookmark ?
|
||||
gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
|
||||
gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle");
|
||||
|
||||
// No description; show the Done, Cancel;
|
||||
// No description; show the Done, Remove;
|
||||
this._element("editBookmarkPanelDescription").textContent = "";
|
||||
this._element("editBookmarkPanelBottomButtons").hidden = false;
|
||||
this._element("editBookmarkPanelContent").hidden = false;
|
||||
|
||||
// The remove button is shown only if we're not already batching, i.e.
|
||||
// if the cancel button/ESC does not remove the bookmark.
|
||||
this._element("editBookmarkPanelRemoveButton").hidden = this._batching;
|
||||
|
||||
// The label of the remove button differs if the URI is bookmarked
|
||||
// multiple times.
|
||||
let bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
|
||||
@ -250,14 +277,8 @@ var StarUI = {
|
||||
gEditItemOverlay.uninitPanel(true);
|
||||
},
|
||||
|
||||
cancelButtonOnCommand: function SU_cancelButtonOnCommand() {
|
||||
this._actionOnHide = "cancel";
|
||||
this.panel.hidePopup(true);
|
||||
},
|
||||
|
||||
removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
|
||||
this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
|
||||
this._actionOnHide = "remove";
|
||||
this.panel.hidePopup();
|
||||
},
|
||||
|
||||
@ -325,7 +346,8 @@ var PlacesCommandHook = {
|
||||
|
||||
var uri = aBrowser.currentURI;
|
||||
var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
|
||||
if (itemId == -1) {
|
||||
let isNewBookmark = itemId == -1;
|
||||
if (isNewBookmark) {
|
||||
// Bug 1148838 - Make this code work for full page plugins.
|
||||
var title;
|
||||
var description;
|
||||
@ -342,10 +364,10 @@ var PlacesCommandHook = {
|
||||
}
|
||||
catch (e) { }
|
||||
|
||||
if (aShowEditUI) {
|
||||
// If we bookmark the page here (i.e. page was not "starred" already)
|
||||
// but open right into the "edit" state, start batching here, so
|
||||
// "Cancel" in that state removes the bookmark.
|
||||
if (aShowEditUI && isNewBookmark) {
|
||||
// If we bookmark the page here but open right into a cancelable
|
||||
// state (i.e. new bookmark in Library), start batching here so
|
||||
// all of the actions can be undone in a single undo step.
|
||||
StarUI.beginBatch();
|
||||
}
|
||||
|
||||
@ -376,16 +398,16 @@ var PlacesCommandHook = {
|
||||
// 3. the content area
|
||||
if (BookmarkingUI.anchor) {
|
||||
StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
|
||||
"bottomcenter topright");
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
return;
|
||||
}
|
||||
|
||||
let identityIcon = document.getElementById("identity-icon");
|
||||
if (isElementVisible(identityIcon)) {
|
||||
StarUI.showEditBookmarkPopup(itemId, identityIcon,
|
||||
"bottomcenter topright");
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
} else {
|
||||
StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap");
|
||||
StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark);
|
||||
}
|
||||
}),
|
||||
|
||||
@ -394,6 +416,7 @@ var PlacesCommandHook = {
|
||||
_bookmarkPagePT: Task.async(function* (aBrowser, aParentId, aShowEditUI) {
|
||||
let url = new URL(aBrowser.currentURI.spec);
|
||||
let info = yield PlacesUtils.bookmarks.fetch({ url });
|
||||
let isNewBookmark = !info;
|
||||
if (!info) {
|
||||
let parentGuid = aParentId !== undefined ?
|
||||
yield PlacesUtils.promiseItemGuid(aParentId) :
|
||||
@ -417,10 +440,10 @@ var PlacesCommandHook = {
|
||||
Components.utils.reportError(e);
|
||||
}
|
||||
|
||||
if (aShowEditUI) {
|
||||
// If we bookmark the page here (i.e. page was not "starred" already)
|
||||
// but open right into the "edit" state, start batching here, so
|
||||
// "Cancel" in that state removes the bookmark.
|
||||
if (aShowEditUI && isNewBookmark) {
|
||||
// If we bookmark the page here but open right into a cancelable
|
||||
// state (i.e. new bookmark in Library), start batching here so
|
||||
// all of the actions can be undone in a single undo step.
|
||||
StarUI.beginBatch();
|
||||
}
|
||||
|
||||
@ -452,16 +475,16 @@ var PlacesCommandHook = {
|
||||
// 3. the content area
|
||||
if (BookmarkingUI.anchor) {
|
||||
StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
|
||||
"bottomcenter topright");
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
return;
|
||||
}
|
||||
|
||||
let identityIcon = document.getElementById("identity-icon");
|
||||
if (isElementVisible(identityIcon)) {
|
||||
StarUI.showEditBookmarkPopup(node, identityIcon,
|
||||
"bottomcenter topright");
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
} else {
|
||||
StarUI.showEditBookmarkPopup(node, aBrowser, "overlap");
|
||||
StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark);
|
||||
}
|
||||
}),
|
||||
|
||||
@ -1703,19 +1726,15 @@ var BookmarkingUI = {
|
||||
let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
|
||||
.forWindow(window);
|
||||
if (widget.overflowed) {
|
||||
// Allow to close the panel if the page is already bookmarked, cause
|
||||
// we are going to open the edit bookmark panel.
|
||||
if (isBookmarked)
|
||||
widget.node.removeAttribute("closemenu");
|
||||
else
|
||||
widget.node.setAttribute("closemenu", "none");
|
||||
// Close the overflow panel because the Edit Bookmark panel will appear.
|
||||
widget.node.removeAttribute("closemenu");
|
||||
}
|
||||
|
||||
// Ignore clicks on the star if we are updating its state.
|
||||
if (!this._pendingStmt) {
|
||||
if (!isBookmarked)
|
||||
this._showBookmarkedNotification();
|
||||
PlacesCommandHook.bookmarkCurrentPage(isBookmarked);
|
||||
PlacesCommandHook.bookmarkCurrentPage(true);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -2345,7 +2345,12 @@ function URLBarSetURI(aURI) {
|
||||
checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) {
|
||||
value = "";
|
||||
} else {
|
||||
value = losslessDecodeURI(uri);
|
||||
// We should deal with losslessDecodeURI throwing for exotic URIs
|
||||
try {
|
||||
value = losslessDecodeURI(uri);
|
||||
} catch (ex) {
|
||||
value = "about:blank";
|
||||
}
|
||||
}
|
||||
|
||||
valid = !isBlankPageURL(uri.spec);
|
||||
|
@ -180,12 +180,6 @@
|
||||
<vbox>
|
||||
<label id="editBookmarkPanelTitle"/>
|
||||
<description id="editBookmarkPanelDescription"/>
|
||||
<hbox>
|
||||
<button id="editBookmarkPanelRemoveButton"
|
||||
class="editBookmarkPanelHeaderButton"
|
||||
oncommand="StarUI.removeBookmarkButtonCommand();"
|
||||
accesskey="&editBookmark.removeBookmark.accessKey;"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</row>
|
||||
<vbox id="editBookmarkPanelContent" flex="1" hidden="true"/>
|
||||
@ -196,15 +190,15 @@
|
||||
label="&editBookmark.done.label;"
|
||||
default="true"
|
||||
oncommand="StarUI.panel.hidePopup();"/>
|
||||
<button id="editBookmarkPanelDeleteButton"
|
||||
<button id="editBookmarkPanelRemoveButton"
|
||||
class="editBookmarkPanelBottomButton"
|
||||
label="&editBookmark.cancel.label;"
|
||||
oncommand="StarUI.cancelButtonOnCommand();"/>
|
||||
oncommand="StarUI.removeBookmarkButtonCommand();"
|
||||
accesskey="&editBookmark.removeBookmark.accessKey;"/>
|
||||
#else
|
||||
<button id="editBookmarkPanelDeleteButton"
|
||||
<button id="editBookmarkPanelRemoveButton"
|
||||
class="editBookmarkPanelBottomButton"
|
||||
label="&editBookmark.cancel.label;"
|
||||
oncommand="StarUI.cancelButtonOnCommand();"/>
|
||||
oncommand="StarUI.removeBookmarkButtonCommand();"
|
||||
accesskey="&editBookmark.removeBookmark.accessKey;"/>
|
||||
<button id="editBookmarkPanelDoneButton"
|
||||
class="editBookmarkPanelBottomButton"
|
||||
label="&editBookmark.done.label;"
|
||||
|
@ -1026,11 +1026,16 @@
|
||||
|
||||
if (!aForceUpdate) {
|
||||
TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
|
||||
if (!Services.appinfo.browserTabsRemoteAutostart) {
|
||||
// old way of measuring tab paint which is not
|
||||
// valid with e10s.
|
||||
window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
|
||||
.beginTabSwitch();
|
||||
if (!gMultiProcessBrowser) {
|
||||
// old way of measuring tab paint which is not valid with e10s.
|
||||
// Waiting until the next MozAfterPaint ensures that we capture
|
||||
// the time it takes to paint, upload the textures to the compositor,
|
||||
// and then composite.
|
||||
TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_MS");
|
||||
window.addEventListener("MozAfterPaint", function onMozAfterPaint() {
|
||||
TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_MS");
|
||||
window.removeEventListener("MozAfterPaint", onMozAfterPaint);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -5891,7 +5896,7 @@
|
||||
bgLoad = !bgLoad;
|
||||
|
||||
let tab = this._getDragTargetTab(event, true);
|
||||
if (!tab || dropEffect == "copy") {
|
||||
if (!tab) {
|
||||
// We're adding a new tab.
|
||||
let newIndex = this._getDropIndex(event, true);
|
||||
let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true});
|
||||
|
@ -8,5 +8,6 @@ support-files =
|
||||
[browser_notification_open_settings.js]
|
||||
[browser_notification_remove_permission.js]
|
||||
[browser_notification_permission_migration.js]
|
||||
[browser_notification_replace.js]
|
||||
[browser_notification_tab_switching.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
|
@ -28,7 +28,7 @@ add_task(function* test_notificationClose() {
|
||||
let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel");
|
||||
is(alertTitleLabel.value, "Test title", "Title text of notification should be present");
|
||||
let alertTextLabel = alertWindow.document.getElementById("alertTextLabel");
|
||||
is(alertTextLabel.textContent, "Test body", "Body text of notification should be present");
|
||||
is(alertTextLabel.textContent, "Test body 2", "Body text of notification should be present");
|
||||
|
||||
let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton");
|
||||
is(alertCloseButton.localName, "toolbarbutton", "close button found");
|
||||
|
@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
|
||||
let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
|
||||
|
||||
add_task(function* test_notificationReplace() {
|
||||
let pm = Services.perms;
|
||||
pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
|
||||
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: notificationURL
|
||||
}, function* dummyTabTask(aBrowser) {
|
||||
yield ContentTask.spawn(aBrowser, {}, function* () {
|
||||
let win = content.window.wrappedJSObject;
|
||||
let notification = win.showNotification1();
|
||||
let promiseCloseEvent = ContentTaskUtils.waitForEvent(notification, "close");
|
||||
|
||||
let showEvent = yield ContentTaskUtils.waitForEvent(notification, "show");
|
||||
is(showEvent.target.body, "Test body 1", "Showed tagged notification");
|
||||
|
||||
let newNotification = win.showNotification2();
|
||||
let newShowEvent = yield ContentTaskUtils.waitForEvent(newNotification, "show");
|
||||
is(newShowEvent.target.body, "Test body 2", "Showed new notification with same tag");
|
||||
|
||||
let closeEvent = yield promiseCloseEvent;
|
||||
is(closeEvent.target.body, "Test body 1", "Closed previous tagged notification");
|
||||
|
||||
let promiseNewCloseEvent = ContentTaskUtils.waitForEvent(newNotification, "close");
|
||||
newNotification.close();
|
||||
let newCloseEvent = yield promiseNewCloseEvent;
|
||||
is(newCloseEvent.target.body, "Test body 2", "Closed new notification");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* cleanup() {
|
||||
Services.perms.remove(makeURI(notificationURL), "desktop-notification");
|
||||
});
|
@ -8,7 +8,7 @@ function showNotification1() {
|
||||
var options = {
|
||||
dir: undefined,
|
||||
lang: undefined,
|
||||
body: "Test body",
|
||||
body: "Test body 1",
|
||||
tag: "Test tag",
|
||||
icon: undefined,
|
||||
};
|
||||
@ -23,7 +23,7 @@ function showNotification2() {
|
||||
var options = {
|
||||
dir: undefined,
|
||||
lang: undefined,
|
||||
body: "Test body",
|
||||
body: "Test body 2",
|
||||
tag: "Test tag",
|
||||
icon: undefined,
|
||||
};
|
||||
|
@ -154,6 +154,7 @@ skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir
|
||||
skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
|
||||
[browser_beforeunload_duplicate_dialogs.js]
|
||||
[browser_blob-channelname.js]
|
||||
[browser_bookmark_popup.js]
|
||||
[browser_bookmark_titles.js]
|
||||
skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
|
||||
[browser_bug304198.js]
|
||||
|
271
browser/base/content/test/general/browser_bookmark_popup.js
Normal file
271
browser/base/content/test/general/browser_bookmark_popup.js
Normal file
@ -0,0 +1,271 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Test opening and closing the bookmarks panel.
|
||||
*/
|
||||
|
||||
let bookmarkPanel = document.getElementById("editBookmarkPanel");
|
||||
let bookmarkStar = document.getElementById("bookmarks-menu-button");
|
||||
let bookmarkPanelTitle = document.getElementById("editBookmarkPanelTitle");
|
||||
|
||||
StarUI._closePanelQuickForTesting = true;
|
||||
Services.prefs.setBoolPref("browser.bookmarks.closePanelQuickForTesting", true);
|
||||
|
||||
function* test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
|
||||
shouldAutoClose, popupHideFn, isBookmarkRemoved}) {
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
|
||||
try {
|
||||
if (!isNewBookmark) {
|
||||
yield PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: "about:home",
|
||||
title: "Home Page"
|
||||
});
|
||||
}
|
||||
|
||||
is(bookmarkStar.hasAttribute("starred"), !isNewBookmark,
|
||||
"Page should only be starred prior to popupshown if editing bookmark");
|
||||
is(bookmarkPanel.state, "closed", "Panel should be 'closed' to start test");
|
||||
let shownPromise = promisePopupShown(bookmarkPanel);
|
||||
yield popupShowFn(tab.linkedBrowser);
|
||||
yield shownPromise;
|
||||
is(bookmarkPanel.state, "open", "Panel should be 'open' after shownPromise is resolved");
|
||||
|
||||
if (popupEditFn) {
|
||||
yield popupEditFn();
|
||||
}
|
||||
let bookmarks = [];
|
||||
yield PlacesUtils.bookmarks.fetch({url: "about:home"}, bm => bookmarks.push(bm));
|
||||
is(bookmarks.length, 1, "Only one bookmark should exist");
|
||||
is(bookmarkStar.getAttribute("starred"), "true", "Page is starred");
|
||||
is(bookmarkPanel.state, "open", "Check that panel state is 'open'");
|
||||
is(bookmarkPanelTitle.value,
|
||||
isNewBookmark ?
|
||||
gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
|
||||
gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle"),
|
||||
"title should match isEditingBookmark state");
|
||||
|
||||
if (!shouldAutoClose) {
|
||||
yield new Promise(resolve => setTimeout(resolve, 400));
|
||||
}
|
||||
|
||||
let hiddenPromise = promisePopupHidden(bookmarkPanel);
|
||||
if (popupHideFn) {
|
||||
yield popupHideFn();
|
||||
}
|
||||
yield hiddenPromise;
|
||||
is(bookmarkStar.hasAttribute("starred"), !isBookmarkRemoved,
|
||||
"Page is starred after closing");
|
||||
} finally {
|
||||
let bookmark = yield PlacesUtils.bookmarks.fetch({url: "about:home"});
|
||||
is(!!bookmark, !isBookmarkRemoved,
|
||||
"bookmark should not be present if a panel action should've removed it");
|
||||
if (bookmark) {
|
||||
yield PlacesUtils.bookmarks.remove(bookmark);
|
||||
}
|
||||
gBrowser.removeTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
add_task(function* panel_shown_for_new_bookmarks_and_autocloses() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: true,
|
||||
popupShowFn() {
|
||||
bookmarkStar.click();
|
||||
},
|
||||
shouldAutoClose: true,
|
||||
isBookmarkRemoved: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* panel_shown_for_once_for_doubleclick_on_new_bookmark_star_and_autocloses() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: true,
|
||||
popupShowFn() {
|
||||
EventUtils.synthesizeMouse(bookmarkStar, 10, 10, { clickCount: 2 },
|
||||
window);
|
||||
},
|
||||
shouldAutoClose: true,
|
||||
isBookmarkRemoved: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* panel_shown_once_for_slow_doubleclick_on_new_bookmark_star_and_autocloses() {
|
||||
todo(false, "bug 1250267, may need to add some tracking state to " +
|
||||
"browser-places.js for this.");
|
||||
return;
|
||||
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: true,
|
||||
*popupShowFn() {
|
||||
EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
|
||||
yield new Promise(resolve => setTimeout(resolve, 300));
|
||||
EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
|
||||
},
|
||||
shouldAutoClose: true,
|
||||
isBookmarkRemoved: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* panel_shown_for_keyboardshortcut_on_new_bookmark_star_and_autocloses() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: true,
|
||||
popupShowFn() {
|
||||
EventUtils.synthesizeKey("D", {accelKey: true}, window);
|
||||
},
|
||||
shouldAutoClose: true,
|
||||
isBookmarkRemoved: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* panel_shown_for_new_bookmarks_mouseover_mouseout() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: true,
|
||||
popupShowFn() {
|
||||
bookmarkStar.click();
|
||||
},
|
||||
*popupEditFn() {
|
||||
let mouseOverPromise = new Promise(resolve => {
|
||||
bookmarkPanel.addEventListener("mouseover", function onmouseover() {
|
||||
bookmarkPanel.removeEventListener("mouseover", onmouseover);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
yield new Promise(resolve => {
|
||||
EventUtils.synthesizeNativeMouseMove(bookmarkPanel, 0, 0, resolve, window);
|
||||
});
|
||||
info("Waiting for mouseover event");
|
||||
yield mouseOverPromise;
|
||||
info("Got mouseover event");
|
||||
|
||||
yield new Promise(resolve => setTimeout(resolve, 400));
|
||||
is(bookmarkPanel.state, "open", "Panel should still be open on mouseover");
|
||||
|
||||
let mouseOutPromise = new Promise(resolve => {
|
||||
bookmarkPanel.addEventListener("mouseout", function onmouseout() {
|
||||
bookmarkPanel.removeEventListener("mouseout", onmouseout);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
yield new Promise(resolve => {
|
||||
EventUtils.synthesizeNativeMouseMove(bookmarkStar, 0, 0, resolve, window);
|
||||
});
|
||||
info("Waiting for mouseout event");
|
||||
yield mouseOutPromise;
|
||||
info("Got mouseout event, should autoclose now");
|
||||
},
|
||||
shouldAutoClose: true,
|
||||
isBookmarkRemoved: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* panel_shown_for_new_bookmark_no_autoclose_close_with_ESC() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: false,
|
||||
popupShowFn() {
|
||||
bookmarkStar.click();
|
||||
},
|
||||
shouldAutoClose: false,
|
||||
popupHideFn() {
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
|
||||
},
|
||||
isBookmarkRemoved: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* panel_shown_for_editing_no_autoclose_close_with_ESC() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: false,
|
||||
popupShowFn() {
|
||||
bookmarkStar.click();
|
||||
},
|
||||
shouldAutoClose: false,
|
||||
popupHideFn() {
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
|
||||
},
|
||||
isBookmarkRemoved: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* panel_shown_for_new_bookmark_keypress_no_autoclose() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: true,
|
||||
popupShowFn() {
|
||||
bookmarkStar.click();
|
||||
},
|
||||
popupEditFn() {
|
||||
EventUtils.sendChar("VK_TAB", window);
|
||||
},
|
||||
shouldAutoClose: false,
|
||||
popupHideFn() {
|
||||
bookmarkPanel.hidePopup();
|
||||
},
|
||||
isBookmarkRemoved: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* contextmenu_new_bookmark_click_no_autoclose() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: true,
|
||||
*popupShowFn(browser) {
|
||||
let contextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu,
|
||||
"popupshown");
|
||||
let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu,
|
||||
"popuphidden");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("body", {
|
||||
type: "contextmenu",
|
||||
button: 2
|
||||
}, browser);
|
||||
yield awaitPopupShown;
|
||||
document.getElementById("context-bookmarkpage").click();
|
||||
contextMenu.hidePopup();
|
||||
yield awaitPopupHidden;
|
||||
},
|
||||
popupEditFn() {
|
||||
bookmarkPanelTitle.click();
|
||||
},
|
||||
shouldAutoClose: false,
|
||||
popupHideFn() {
|
||||
bookmarkPanel.hidePopup();
|
||||
},
|
||||
isBookmarkRemoved: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* bookmarks_menu_new_bookmark_remove_bookmark() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: true,
|
||||
popupShowFn(browser) {
|
||||
document.getElementById("menu_bookmarkThisPage").doCommand();
|
||||
},
|
||||
shouldAutoClose: true,
|
||||
popupHideFn() {
|
||||
document.getElementById("editBookmarkPanelRemoveButton").click();
|
||||
},
|
||||
isBookmarkRemoved: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* ctrl_d_edit_bookmark_remove_bookmark() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: false,
|
||||
popupShowFn(browser) {
|
||||
EventUtils.synthesizeKey("D", {accelKey: true}, window);
|
||||
},
|
||||
shouldAutoClose: true,
|
||||
popupHideFn() {
|
||||
document.getElementById("editBookmarkPanelRemoveButton").click();
|
||||
},
|
||||
isBookmarkRemoved: true,
|
||||
});
|
||||
});
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("browser.bookmarks.closePanelQuickForTesting");
|
||||
delete StarUI._closePanelQuickForTesting;
|
||||
})
|
@ -60,6 +60,9 @@ function test() {
|
||||
testVal("<user:pass@sub1.sub2.sub3.>mozilla.org");
|
||||
testVal("<user:pass@>mozilla.org");
|
||||
|
||||
testVal("<https://>mozilla.org< >");
|
||||
testVal("mozilla.org< >");
|
||||
|
||||
testVal("<https://>mozilla.org</file.ext>");
|
||||
testVal("<https://>mozilla.org</sub/file.ext>");
|
||||
testVal("<https://>mozilla.org</sub/file.ext?foo>");
|
||||
|
@ -1,3 +1,4 @@
|
||||
[browser_moz_action_link.js]
|
||||
[browser_urlbar_blanking.js]
|
||||
support-files =
|
||||
file_blank_but_not_blank.html
|
||||
|
31
browser/base/content/test/urlbar/browser_moz_action_link.js
Normal file
31
browser/base/content/test/urlbar/browser_moz_action_link.js
Normal file
@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
|
||||
const kURIs = [
|
||||
"moz-action:foo,",
|
||||
"moz-action:foo",
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
for (let uri of kURIs) {
|
||||
let dataURI = `data:text/html,<a id=a href="${uri}" target=_blank>Link</a>`;
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI);
|
||||
|
||||
let tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, function() {});
|
||||
yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
|
||||
content.document.getElementById("a").click();
|
||||
});
|
||||
yield tabSwitchPromise;
|
||||
isnot(gBrowser.selectedTab, tab, "Switched to new tab!");
|
||||
is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank");
|
||||
let newTab = gBrowser.selectedTab;
|
||||
yield BrowserTestUtils.switchTab(gBrowser, tab);
|
||||
yield BrowserTestUtils.switchTab(gBrowser, newTab);
|
||||
is(gBrowser.selectedTab, newTab, "Switched to new tab again!");
|
||||
is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank after tab switch");
|
||||
// Finally, check that directly setting it produces the right results, too:
|
||||
URLBarSetURI(makeURI(uri));
|
||||
is(gURLBar.value, "about:blank", "URL bar should still be displaying about:blank");
|
||||
yield BrowserTestUtils.removeTab(newTab);
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
});
|
@ -261,7 +261,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
trimmedLength = "http://".length;
|
||||
}
|
||||
|
||||
let matchedURL = value.match(/^((?:[a-z]+:\/\/)(?:[^\/#?]+@)?)(.+?)(?::\d+)?(?:[\/#?]|$)/);
|
||||
let matchedURL = value.match(/^((?:[a-z]+:\/\/)(?:[^\/#?]+@)?)(\S+?)(?::\d+)?\s*(?:[\/#?]|$)/);
|
||||
if (!matchedURL)
|
||||
return;
|
||||
|
||||
@ -839,12 +839,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
<method name="_parseActionUrl">
|
||||
<parameter name="aUrl"/>
|
||||
<body><![CDATA[
|
||||
if (!aUrl.startsWith("moz-action:"))
|
||||
const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
|
||||
if (!MOZ_ACTION_REGEX.test(aUrl))
|
||||
return null;
|
||||
|
||||
// URL is in the format moz-action:ACTION,PARAMS
|
||||
// Where PARAMS is a JSON encoded object.
|
||||
let [, type, params] = aUrl.match(/^moz-action:([^,]+),(.*)$/);
|
||||
let [, type, params] = aUrl.match(MOZ_ACTION_REGEX);
|
||||
|
||||
let action = {
|
||||
type: type,
|
||||
|
@ -33,7 +33,6 @@ skip-if = os == "mac"
|
||||
skip-if = os == "linux"
|
||||
|
||||
[browser_901207_searchbar_in_panel.js]
|
||||
skip-if = e10s # bug 1090656
|
||||
[browser_913972_currentset_overflow.js]
|
||||
skip-if = os == "linux"
|
||||
|
||||
@ -69,9 +68,9 @@ skip-if = os == "linux"
|
||||
[browser_947914_button_addons.js]
|
||||
skip-if = os == "linux" # Intermittent failures
|
||||
[browser_947914_button_copy.js]
|
||||
skip-if = os == "linux" || e10s # Intermittent failures on Linux, e10s issues are bug 1091561
|
||||
skip-if = os == "linux" # Intermittent failures on Linux
|
||||
[browser_947914_button_cut.js]
|
||||
skip-if = os == "linux" || e10s # Intermittent failures on Linux, e10s issues are bug 1091561
|
||||
skip-if = os == "linux" # Intermittent failures on Linux
|
||||
[browser_947914_button_find.js]
|
||||
skip-if = os == "linux" # Intermittent failures
|
||||
[browser_947914_button_history.js]
|
||||
@ -81,9 +80,9 @@ skip-if = os == "linux" # Intermittent failures
|
||||
[browser_947914_button_newWindow.js]
|
||||
skip-if = os == "linux" # Intermittent failures
|
||||
[browser_947914_button_paste.js]
|
||||
skip-if = os == "linux" || e10s # Intermittent failures on Linux, e10s issues are bug 1091561
|
||||
skip-if = os == "linux" # Intermittent failures on Linux
|
||||
[browser_947914_button_print.js]
|
||||
skip-if = os == "linux" || (os == "win" && e10s) # Intermittent failures on Linux, e10s issues on Windows (bug 1088714)
|
||||
skip-if = os == "linux" # Intermittent failures on Linux
|
||||
[browser_947914_button_savePage.js]
|
||||
skip-if = os == "linux" # Intermittent failures
|
||||
[browser_947914_button_zoomIn.js]
|
||||
@ -123,7 +122,6 @@ skip-if = os == "linux"
|
||||
[browser_984455_bookmarks_items_reparenting.js]
|
||||
skip-if = os == "linux"
|
||||
[browser_985815_propagate_setToolbarVisibility.js]
|
||||
skip-if = e10s # bug 1090635
|
||||
[browser_987177_destroyWidget_xul.js]
|
||||
[browser_987177_xul_wrapper_updating.js]
|
||||
[browser_987185_syncButton.js]
|
||||
@ -133,7 +131,6 @@ skip-if = e10s # Bug 1088710
|
||||
[browser_988072_sidebar_events.js]
|
||||
[browser_989338_saved_placements_not_resaved.js]
|
||||
[browser_989751_subviewbutton_class.js]
|
||||
skip-if = os == "linux" && e10s # Bug 1102900, bug 1104745, bug 1104761
|
||||
[browser_992747_toggle_noncustomizable_toolbar.js]
|
||||
[browser_993322_widget_notoolbar.js]
|
||||
[browser_995164_registerArea_during_customize_mode.js]
|
||||
|
@ -8,57 +8,52 @@ var initialLocation = gBrowser.currentURI.spec;
|
||||
var globalClipboard;
|
||||
|
||||
add_task(function*() {
|
||||
info("Check copy button existence and functionality");
|
||||
yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
|
||||
info("Check copy button existence and functionality");
|
||||
|
||||
let testText = "copy text test";
|
||||
let testText = "copy text test";
|
||||
|
||||
gURLBar.focus();
|
||||
info("The URL bar was focused");
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
gURLBar.focus();
|
||||
info("The URL bar was focused");
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
|
||||
let copyButton = document.getElementById("copy-button");
|
||||
ok(copyButton, "Copy button exists in Panel Menu");
|
||||
ok(copyButton.getAttribute("disabled"), "Copy button is initially disabled");
|
||||
let copyButton = document.getElementById("copy-button");
|
||||
ok(copyButton, "Copy button exists in Panel Menu");
|
||||
ok(copyButton.getAttribute("disabled"), "Copy button is initially disabled");
|
||||
|
||||
// copy text from URL bar
|
||||
gURLBar.value = testText;
|
||||
gURLBar.focus();
|
||||
gURLBar.select();
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
// copy text from URL bar
|
||||
gURLBar.value = testText;
|
||||
gURLBar.focus();
|
||||
gURLBar.select();
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
|
||||
ok(!copyButton.hasAttribute("disabled"), "Copy button is enabled when selecting");
|
||||
ok(!copyButton.hasAttribute("disabled"), "Copy button is enabled when selecting");
|
||||
|
||||
copyButton.click();
|
||||
is(gURLBar.value, testText, "Selected text is unaltered when clicking copy");
|
||||
copyButton.click();
|
||||
is(gURLBar.value, testText, "Selected text is unaltered when clicking copy");
|
||||
|
||||
// check that the text was added to the clipboard
|
||||
let clipboard = Services.clipboard;
|
||||
let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
|
||||
globalClipboard = clipboard.kGlobalClipboard;
|
||||
// check that the text was added to the clipboard
|
||||
let clipboard = Services.clipboard;
|
||||
let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
|
||||
globalClipboard = clipboard.kGlobalClipboard;
|
||||
|
||||
transferable.init(null);
|
||||
transferable.addDataFlavor("text/unicode");
|
||||
clipboard.getData(transferable, globalClipboard);
|
||||
let str = {}, strLength = {};
|
||||
transferable.getTransferData("text/unicode", str, strLength);
|
||||
let clipboardValue = "";
|
||||
transferable.init(null);
|
||||
transferable.addDataFlavor("text/unicode");
|
||||
clipboard.getData(transferable, globalClipboard);
|
||||
let str = {}, strLength = {};
|
||||
transferable.getTransferData("text/unicode", str, strLength);
|
||||
let clipboardValue = "";
|
||||
|
||||
if (str.value) {
|
||||
str.value.QueryInterface(Ci.nsISupportsString);
|
||||
clipboardValue = str.value.data;
|
||||
}
|
||||
is(clipboardValue, testText, "Data was copied to the clipboard.");
|
||||
if (str.value) {
|
||||
str.value.QueryInterface(Ci.nsISupportsString);
|
||||
clipboardValue = str.value.data;
|
||||
}
|
||||
is(clipboardValue, testText, "Data was copied to the clipboard.");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* asyncCleanup() {
|
||||
// clear the clipboard
|
||||
registerCleanupFunction(function cleanup() {
|
||||
Services.clipboard.emptyClipboard(globalClipboard);
|
||||
info("Clipboard was cleared");
|
||||
|
||||
// restore the tab as it was at the begining of the test
|
||||
gBrowser.addTab(initialLocation);
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
info("Tabs were restored");
|
||||
});
|
||||
|
@ -8,55 +8,50 @@ var initialLocation = gBrowser.currentURI.spec;
|
||||
var globalClipboard;
|
||||
|
||||
add_task(function*() {
|
||||
info("Check cut button existence and functionality");
|
||||
yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
|
||||
info("Check cut button existence and functionality");
|
||||
|
||||
let testText = "cut text test";
|
||||
let testText = "cut text test";
|
||||
|
||||
gURLBar.focus();
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
gURLBar.focus();
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
|
||||
let cutButton = document.getElementById("cut-button");
|
||||
ok(cutButton, "Cut button exists in Panel Menu");
|
||||
ok(cutButton.hasAttribute("disabled"), "Cut button is disabled");
|
||||
let cutButton = document.getElementById("cut-button");
|
||||
ok(cutButton, "Cut button exists in Panel Menu");
|
||||
ok(cutButton.hasAttribute("disabled"), "Cut button is disabled");
|
||||
|
||||
// cut text from URL bar
|
||||
gURLBar.value = testText;
|
||||
gURLBar.focus();
|
||||
gURLBar.select();
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
// cut text from URL bar
|
||||
gURLBar.value = testText;
|
||||
gURLBar.focus();
|
||||
gURLBar.select();
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
|
||||
ok(!cutButton.hasAttribute("disabled"), "Cut button is enabled when selecting");
|
||||
cutButton.click();
|
||||
is(gURLBar.value, "", "Selected text is removed from source when clicking on cut");
|
||||
ok(!cutButton.hasAttribute("disabled"), "Cut button is enabled when selecting");
|
||||
cutButton.click();
|
||||
is(gURLBar.value, "", "Selected text is removed from source when clicking on cut");
|
||||
|
||||
// check that the text was added to the clipboard
|
||||
let clipboard = Services.clipboard;
|
||||
let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
|
||||
globalClipboard = clipboard.kGlobalClipboard;
|
||||
// check that the text was added to the clipboard
|
||||
let clipboard = Services.clipboard;
|
||||
let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
|
||||
globalClipboard = clipboard.kGlobalClipboard;
|
||||
|
||||
transferable.init(null);
|
||||
transferable.addDataFlavor("text/unicode");
|
||||
clipboard.getData(transferable, globalClipboard);
|
||||
let str = {}, strLength = {};
|
||||
transferable.getTransferData("text/unicode", str, strLength);
|
||||
let clipboardValue = "";
|
||||
transferable.init(null);
|
||||
transferable.addDataFlavor("text/unicode");
|
||||
clipboard.getData(transferable, globalClipboard);
|
||||
let str = {}, strLength = {};
|
||||
transferable.getTransferData("text/unicode", str, strLength);
|
||||
let clipboardValue = "";
|
||||
|
||||
if (str.value) {
|
||||
str.value.QueryInterface(Ci.nsISupportsString);
|
||||
clipboardValue = str.value.data;
|
||||
}
|
||||
is(clipboardValue, testText, "Data was copied to the clipboard.");
|
||||
if (str.value) {
|
||||
str.value.QueryInterface(Ci.nsISupportsString);
|
||||
clipboardValue = str.value.data;
|
||||
}
|
||||
is(clipboardValue, testText, "Data was copied to the clipboard.");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* asyncCleanup() {
|
||||
// clear the clipboard
|
||||
registerCleanupFunction(function cleanup() {
|
||||
Services.clipboard.emptyClipboard(globalClipboard);
|
||||
info("Clipboard was cleared");
|
||||
|
||||
// restore the tab as it was at the begining of the test
|
||||
gBrowser.addTab(initialLocation);
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
info("Tabs were restored");
|
||||
});
|
||||
|
@ -8,40 +8,34 @@ var initialLocation = gBrowser.currentURI.spec;
|
||||
var globalClipboard;
|
||||
|
||||
add_task(function*() {
|
||||
info("Check paste button existence and functionality");
|
||||
yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
|
||||
info("Check paste button existence and functionality");
|
||||
|
||||
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
|
||||
globalClipboard = Services.clipboard.kGlobalClipboard;
|
||||
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
|
||||
globalClipboard = Services.clipboard.kGlobalClipboard;
|
||||
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
|
||||
let pasteButton = document.getElementById("paste-button");
|
||||
ok(pasteButton, "Paste button exists in Panel Menu");
|
||||
let pasteButton = document.getElementById("paste-button");
|
||||
ok(pasteButton, "Paste button exists in Panel Menu");
|
||||
|
||||
// add text to clipboard
|
||||
let text = "Sample text for testing";
|
||||
clipboard.copyString(text);
|
||||
// add text to clipboard
|
||||
let text = "Sample text for testing";
|
||||
clipboard.copyString(text);
|
||||
|
||||
// test paste button by pasting text to URL bar
|
||||
gURLBar.focus();
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
// test paste button by pasting text to URL bar
|
||||
gURLBar.focus();
|
||||
yield PanelUI.show();
|
||||
info("Menu panel was opened");
|
||||
|
||||
ok(!pasteButton.hasAttribute("disabled"), "Paste button is enabled");
|
||||
pasteButton.click();
|
||||
ok(!pasteButton.hasAttribute("disabled"), "Paste button is enabled");
|
||||
pasteButton.click();
|
||||
|
||||
is(gURLBar.value, text, "Text pasted successfully");
|
||||
is(gURLBar.value, text, "Text pasted successfully");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* asyncCleanup() {
|
||||
// clear the clipboard
|
||||
registerCleanupFunction(function cleanup() {
|
||||
Services.clipboard.emptyClipboard(globalClipboard);
|
||||
info("Clipboard was cleared");
|
||||
|
||||
// restore the tab as it was at the begining of the test
|
||||
gBrowser.addTab(initialLocation);
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
info("Tabs were restored");
|
||||
});
|
||||
|
||||
|
@ -234,8 +234,21 @@ if (typeof Mozilla == 'undefined') {
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.showFirefoxAccounts = function() {
|
||||
_sendEvent('showFirefoxAccounts');
|
||||
/**
|
||||
* Request the browser open the Firefox Accounts page.
|
||||
*
|
||||
* @param {Object} extraURLCampaignParams - An object containing additional
|
||||
* paramaters for the URL opened by the browser for reasons of promotional
|
||||
* campaign tracking. Each attribute of the object must have a name that
|
||||
* begins with "utm_" and a value that is a string. Both the name and value
|
||||
* must contain only alphanumeric characters, dashes or underscores (meaning
|
||||
* that you are limited to values that don't need encoding, as any such
|
||||
* characters in the name or value will be rejected.)
|
||||
*/
|
||||
Mozilla.UITour.showFirefoxAccounts = function(extraURLCampaignParams) {
|
||||
_sendEvent('showFirefoxAccounts', {
|
||||
extraURLCampaignParams: JSON.stringify(extraURLCampaignParams),
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.resetFirefox = function() {
|
||||
|
@ -610,8 +610,15 @@ this.UITour = {
|
||||
case "showFirefoxAccounts": {
|
||||
// 'signup' is the only action that makes sense currently, so we don't
|
||||
// accept arbitrary actions just to be safe...
|
||||
let p = new URLSearchParams("action=signup&entrypoint=uitour");
|
||||
// Call our helper to validate extraURLCampaignParams and populate URLSearchParams
|
||||
if (!this._populateCampaignParams(p, data.extraURLCampaignParams)) {
|
||||
log.warn("showFirefoxAccounts: invalid campaign args specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
// We want to replace the current tab.
|
||||
browser.loadURI("about:accounts?action=signup&entrypoint=uitour");
|
||||
browser.loadURI("about:accounts?" + p.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
@ -805,6 +812,52 @@ this.UITour = {
|
||||
}
|
||||
},
|
||||
|
||||
// Given a string that is a JSONified represenation of an object with
|
||||
// additional utm_* URL params that should be appended, validate and append
|
||||
// them to the passed URLSearchParams object. Returns true if the params
|
||||
// were validated and appended, and false if the request should be ignored.
|
||||
_populateCampaignParams: function(urlSearchParams, extraURLCampaignParams) {
|
||||
// We are extra paranoid about what params we allow to be appended.
|
||||
if (typeof extraURLCampaignParams == "undefined") {
|
||||
// no params, so it's all good.
|
||||
return true;
|
||||
}
|
||||
if (typeof extraURLCampaignParams != "string") {
|
||||
log.warn("_populateCampaignParams: extraURLCampaignParams is not a string");
|
||||
return false;
|
||||
}
|
||||
let campaignParams;
|
||||
try {
|
||||
if (extraURLCampaignParams) {
|
||||
campaignParams = JSON.parse(extraURLCampaignParams);
|
||||
if (typeof campaignParams != "object") {
|
||||
log.warn("_populateCampaignParams: extraURLCampaignParams is not a stringified object");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
log.warn("_populateCampaignParams: extraURLCampaignParams is not a JSON object");
|
||||
return false;
|
||||
}
|
||||
if (campaignParams) {
|
||||
// The regex that both the name and value of each param must match.
|
||||
let reSimpleString = /^[-_a-zA-Z0-9]*$/;
|
||||
for (let name in campaignParams) {
|
||||
let value = campaignParams[name];
|
||||
if (typeof name != "string" || typeof value != "string" ||
|
||||
!name.startsWith("utm_") ||
|
||||
value.length == 0 ||
|
||||
!reSimpleString.test(name) ||
|
||||
!reSimpleString.test(value)) {
|
||||
log.warn("_populateCampaignParams: invalid campaign param specified");
|
||||
return false;
|
||||
}
|
||||
urlSearchParams.append(name, value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
setTelemetryBucket: function(aPageID) {
|
||||
let bucket = BUCKET_NAME + BrowserUITelemetry.BUCKET_SEPARATOR + aPageID;
|
||||
BrowserUITelemetry.setBucket(bucket);
|
||||
@ -1700,7 +1753,7 @@ this.UITour = {
|
||||
|
||||
// An event object is expected but we don't want to toggle the panel with a click if the panel
|
||||
// is already open.
|
||||
aWindow.LoopUI.openCallPanel({ target: toolbarButton.node, }, "rooms").then(() => {
|
||||
aWindow.LoopUI.openPanel({ target: toolbarButton.node, }, "rooms").then(() => {
|
||||
if (aOpenCallback) {
|
||||
aOpenCallback();
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly
|
||||
skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly.
|
||||
[browser_UITour_loop.js]
|
||||
skip-if = true # Bug 1225832 - New Loop architecture is not compatible with test.
|
||||
[browser_UITour_loop_panel.js]
|
||||
[browser_UITour_modalDialog.js]
|
||||
skip-if = os != "mac" || e10s # modal dialog disabling only working on OS X. Bug 1240747 - UITour.jsm not e10s friendly
|
||||
[browser_UITour_observe.js]
|
||||
|
@ -112,29 +112,6 @@ var tests = [
|
||||
|
||||
checkLoopPanelIsHidden();
|
||||
}),
|
||||
taskify(function* test_menu_show_hide() {
|
||||
// The targets to highlight only appear after getting started is launched.
|
||||
// Set latestFTUVersion to lower number to show FTU panel.
|
||||
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
|
||||
is(loopButton.open, false, "Menu should initially be closed");
|
||||
gContentAPI.showMenu("loop");
|
||||
|
||||
yield waitForConditionPromise(() => {
|
||||
return loopButton.open;
|
||||
}, "Menu should be visible after showMenu()");
|
||||
|
||||
ok(loopPanel.hasAttribute("noautohide"), "@noautohide should be on the loop panel");
|
||||
ok(loopPanel.hasAttribute("panelopen"), "The panel should have @panelopen");
|
||||
is(loopPanel.state, "open", "The panel should be open");
|
||||
ok(loopButton.hasAttribute("open"), "Loop button should know that the menu is open");
|
||||
|
||||
gContentAPI.hideMenu("loop");
|
||||
yield waitForConditionPromise(() => {
|
||||
return !loopButton.open;
|
||||
}, "Menu should be hidden after hideMenu()");
|
||||
|
||||
checkLoopPanelIsHidden();
|
||||
}),
|
||||
// Test the menu was cleaned up in teardown.
|
||||
taskify(function* setup_menu_cleanup() {
|
||||
gContentAPI.showMenu("loop");
|
||||
|
68
browser/components/uitour/test/browser_UITour_loop_panel.js
Normal file
68
browser/components/uitour/test/browser_UITour_loop_panel.js
Normal file
@ -0,0 +1,68 @@
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
var gMessageHandlers;
|
||||
var loopButton;
|
||||
var fakeRoom;
|
||||
var loopPanel = document.getElementById("loop-notification-panel");
|
||||
|
||||
const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
|
||||
const { LoopRooms } = Cu.import("chrome://loop/content/modules/LoopRooms.jsm", {});
|
||||
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
ok(true, "Loop is disabled so skip the UITour Loop tests");
|
||||
} else {
|
||||
function checkLoopPanelIsHidden() {
|
||||
ok(!loopPanel.hasAttribute("noautohide"), "@noautohide on the loop panel should have been cleaned up");
|
||||
ok(!loopPanel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");
|
||||
isnot(loopPanel.state, "open", "The panel shouldn't be open");
|
||||
is(loopButton.hasAttribute("open"), false, "Loop button should know that the panel is closed");
|
||||
}
|
||||
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
add_task(function() {
|
||||
loopButton = window.LoopUI.toolbarButton.node;
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("loop.gettingStarted.latestFTUVersion");
|
||||
Services.io.offline = false;
|
||||
|
||||
// Copied from browser/components/loop/test/mochitest/head.js
|
||||
// Remove the iframe after each test. This also avoids mochitest complaining
|
||||
// about leaks on shutdown as we intentionally hold the iframe open for the
|
||||
// life of the application.
|
||||
let frameId = loopButton.getAttribute("notificationFrameId");
|
||||
let frame = document.getElementById(frameId);
|
||||
if (frame) {
|
||||
frame.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_menu_show_hide() {
|
||||
// The targets to highlight only appear after getting started is launched.
|
||||
// Set latestFTUVersion to lower number to show FTU panel.
|
||||
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
|
||||
is(loopButton.open, false, "Menu should initially be closed");
|
||||
gContentAPI.showMenu("loop");
|
||||
|
||||
yield waitForConditionPromise(() => {
|
||||
return loopButton.open;
|
||||
}, "Menu should be visible after showMenu()");
|
||||
|
||||
ok(loopPanel.hasAttribute("noautohide"), "@noautohide should be on the loop panel");
|
||||
ok(loopPanel.hasAttribute("panelopen"), "The panel should have @panelopen");
|
||||
is(loopPanel.state, "open", "The panel should be open");
|
||||
ok(loopButton.hasAttribute("open"), "Loop button should know that the menu is open");
|
||||
|
||||
gContentAPI.hideMenu("loop");
|
||||
yield waitForConditionPromise(() => {
|
||||
return !loopButton.open;
|
||||
}, "Menu should be hidden after hideMenu()");
|
||||
|
||||
checkLoopPanelIsHidden();
|
||||
});
|
||||
}
|
@ -22,8 +22,50 @@ add_UITour_task(function* test_checkSyncSetup_enabled() {
|
||||
});
|
||||
|
||||
// The showFirefoxAccounts API is sync related, so we test that here too...
|
||||
add_UITour_task(function* test_firefoxAccounts() {
|
||||
add_UITour_task(function* test_firefoxAccountsNoParams() {
|
||||
yield gContentAPI.showFirefoxAccounts();
|
||||
yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
|
||||
"about:accounts?action=signup&entrypoint=uitour");
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsValidParams() {
|
||||
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", utm_bar: "bar" });
|
||||
yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
|
||||
"about:accounts?action=signup&entrypoint=uitour&utm_foo=foo&utm_bar=bar");
|
||||
});
|
||||
|
||||
// A helper to check the request was ignored due to invalid params.
|
||||
function* checkAboutAccountsNotLoaded() {
|
||||
try {
|
||||
yield waitForConditionPromise(() => {
|
||||
return gBrowser.selectedBrowser.currentURI.spec.startsWith("about:accounts");
|
||||
}, "Check if about:accounts opened");
|
||||
ok(false, "No about:accounts tab should have opened");
|
||||
} catch (ex) {
|
||||
ok(true, "No about:accounts tab opened");
|
||||
}
|
||||
}
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsNonObject() {
|
||||
// non-string should be rejected.
|
||||
yield gContentAPI.showFirefoxAccounts(99);
|
||||
yield checkAboutAccountsNotLoaded();
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsNonUtmPrefix() {
|
||||
// Any non "utm_" name should should be rejected.
|
||||
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", bar: "bar" });
|
||||
yield checkAboutAccountsNotLoaded();
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsNonAlphaName() {
|
||||
// Any "utm_" name which includes non-alpha chars should be rejected.
|
||||
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", "utm_bar=": "bar" });
|
||||
yield checkAboutAccountsNotLoaded();
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsNonAlphaValue() {
|
||||
// Any non-alpha value should be rejected.
|
||||
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo&" });
|
||||
yield checkAboutAccountsNotLoaded();
|
||||
});
|
||||
|
@ -28,6 +28,7 @@ LOOPDIR=browser/extensions/loop
|
||||
|
||||
TESTS="
|
||||
${LOOPDIR}/chrome/test/mochitest
|
||||
browser/components/uitour/test/browser_UITour_loop_panel.js
|
||||
browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
|
||||
browser/base/content/test/general/browser_parsable_css.js
|
||||
"
|
||||
|
4
browser/extensions/pocket/bootstrap.js
vendored
4
browser/extensions/pocket/bootstrap.js
vendored
@ -357,7 +357,7 @@ var PocketOverlay = {
|
||||
PocketContextMenu.init();
|
||||
PocketReader.startup();
|
||||
|
||||
if (reason == ADDON_ENABLE) {
|
||||
if (reason != APP_STARTUP) {
|
||||
for (let win of allBrowserWindows()) {
|
||||
this.setWindowScripts(win);
|
||||
this.addStyles(win);
|
||||
@ -529,7 +529,7 @@ function startup(data, reason) {
|
||||
function shutdown(data, reason) {
|
||||
// For speed sake, we should only do a shutdown if we're being disabled.
|
||||
// On an app shutdown, just let it fade away...
|
||||
if (reason == ADDON_DISABLE) {
|
||||
if (reason != APP_SHUTDOWN) {
|
||||
Services.prefs.removeObserver("extensions.pocket.enabled", prefObserver);
|
||||
PocketOverlay.shutdown(reason);
|
||||
}
|
||||
|
@ -744,7 +744,6 @@ you can use these alternative items. Otherwise, their values should be empty. -
|
||||
<!ENTITY spellAddDictionaries.accesskey "A">
|
||||
|
||||
<!ENTITY editBookmark.done.label "Done">
|
||||
<!ENTITY editBookmark.cancel.label "Cancel">
|
||||
<!ENTITY editBookmark.removeBookmark.accessKey "R">
|
||||
|
||||
<!ENTITY identity.connectionSecure "Secure Connection">
|
||||
|
@ -265,8 +265,8 @@
|
||||
// Disallow unreachable statements after a return, throw, continue, or break
|
||||
// statement.
|
||||
"no-unreachable": 2,
|
||||
// Disallow declaration of variables that are not used in the code
|
||||
"no-unused-vars": 2,
|
||||
// Disallow global and local variables that aren't used, but allow unused function arguments.
|
||||
"no-unused-vars": [2, {"vars": "all", "args": "none"}],
|
||||
// Allow using variables before they are defined.
|
||||
"no-use-before-define": 0,
|
||||
// We use var-only-at-top-level instead of no-var as we allow top level
|
||||
|
@ -43,13 +43,6 @@ button {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
/* Prefs */
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Targets */
|
||||
|
||||
.targets {
|
||||
@ -88,3 +81,8 @@ label {
|
||||
.addons-options {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.addons-debugging-label {
|
||||
display: inline-block;
|
||||
margin: 0 5px 5px 0;
|
||||
}
|
@ -17,6 +17,9 @@ loader.lazyImporter(this, "AddonManager",
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const MORE_INFO_URL = "https://developer.mozilla.org/docs/Tools" +
|
||||
"/about:debugging#Enabling_add-on_debugging";
|
||||
|
||||
exports.AddonsControls = React.createClass({
|
||||
displayName: "AddonsControls",
|
||||
|
||||
@ -33,9 +36,14 @@ exports.AddonsControls = React.createClass({
|
||||
onChange: this.onEnableAddonDebuggingChange,
|
||||
}),
|
||||
React.createElement("label", {
|
||||
className: "addons-debugging-label",
|
||||
htmlFor: "enable-addon-debugging",
|
||||
title: Strings.GetStringFromName("addonDebugging.tooltip")
|
||||
}, Strings.GetStringFromName("addonDebugging.label"))
|
||||
}, Strings.GetStringFromName("addonDebugging.label")),
|
||||
"(",
|
||||
React.createElement("a", { href: MORE_INFO_URL, target: "_blank" },
|
||||
Strings.GetStringFromName("addonDebugging.moreInfo")),
|
||||
")"
|
||||
),
|
||||
React.createElement("button", {
|
||||
id: "load-addon-from-file",
|
||||
@ -47,6 +55,7 @@ exports.AddonsControls = React.createClass({
|
||||
onEnableAddonDebuggingChange(event) {
|
||||
let enabled = event.target.checked;
|
||||
Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
|
||||
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
|
||||
},
|
||||
|
||||
loadAddonFromFile(event) {
|
||||
|
@ -23,6 +23,9 @@ const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
|
||||
const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
|
||||
|
||||
exports.AddonsTab = React.createClass({
|
||||
displayName: "AddonsTab",
|
||||
|
||||
@ -35,15 +38,21 @@ exports.AddonsTab = React.createClass({
|
||||
|
||||
componentDidMount() {
|
||||
AddonManager.addAddonListener(this);
|
||||
Services.prefs.addObserver("devtools.chrome.enabled",
|
||||
|
||||
Services.prefs.addObserver(CHROME_ENABLED_PREF,
|
||||
this.updateDebugStatus, false);
|
||||
Services.prefs.addObserver(REMOTE_ENABLED_PREF,
|
||||
this.updateDebugStatus, false);
|
||||
|
||||
this.updateDebugStatus();
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
AddonManager.removeAddonListener(this);
|
||||
Services.prefs.removeObserver("devtools.chrome.enabled",
|
||||
Services.prefs.removeObserver(CHROME_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
Services.prefs.removeObserver(REMOTE_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
},
|
||||
|
||||
@ -68,9 +77,11 @@ exports.AddonsTab = React.createClass({
|
||||
},
|
||||
|
||||
updateDebugStatus() {
|
||||
this.setState({
|
||||
debugDisabled: !Services.prefs.getBoolPref("devtools.chrome.enabled")
|
||||
});
|
||||
let debugDisabled =
|
||||
!Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
|
||||
!Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
|
||||
|
||||
this.setState({ debugDisabled });
|
||||
},
|
||||
|
||||
updateAddonsList() {
|
||||
|
@ -8,6 +8,7 @@ support-files =
|
||||
service-workers/empty-sw.html
|
||||
service-workers/empty-sw.js
|
||||
|
||||
[browser_addons_debugging_initial_state.js]
|
||||
[browser_addons_install.js]
|
||||
[browser_addons_toggle_debug.js]
|
||||
[browser_service_workers.js]
|
||||
|
@ -0,0 +1,67 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that addons debugging controls are properly enabled/disabled depending
|
||||
// on the values of the relevant preferences:
|
||||
// - devtools.chrome.enabled
|
||||
// - devtools.debugger.remote-enabled
|
||||
|
||||
const ADDON_ID = "test-devtools@mozilla.org";
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
chromeEnabled: false,
|
||||
debuggerRemoteEnable: false,
|
||||
expected: false,
|
||||
}, {
|
||||
chromeEnabled: false,
|
||||
debuggerRemoteEnable: true,
|
||||
expected: false,
|
||||
}, {
|
||||
chromeEnabled: true,
|
||||
debuggerRemoteEnable: false,
|
||||
expected: false,
|
||||
}, {
|
||||
chromeEnabled: true,
|
||||
debuggerRemoteEnable: true,
|
||||
expected: true,
|
||||
}
|
||||
];
|
||||
|
||||
add_task(function* () {
|
||||
for (let testData of TEST_DATA) {
|
||||
yield testCheckboxState(testData);
|
||||
}
|
||||
});
|
||||
|
||||
function* testCheckboxState(testData) {
|
||||
info("Set preferences as defined by the current test data.");
|
||||
yield new Promise(resolve => {
|
||||
let options = {"set": [
|
||||
["devtools.chrome.enabled", testData.chromeEnabled],
|
||||
["devtools.debugger.remote-enabled", testData.debuggerRemoteEnable],
|
||||
]};
|
||||
SpecialPowers.pushPrefEnv(options, resolve);
|
||||
});
|
||||
|
||||
let { tab, document } = yield openAboutDebugging("addons");
|
||||
|
||||
info("Install a test addon.");
|
||||
yield installAddon(document, "addons/unpacked/install.rdf", "test-devtools");
|
||||
|
||||
info("Test checkbox checked state.");
|
||||
let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
|
||||
is(addonDebugCheckbox.checked, testData.expected,
|
||||
"Addons debugging checkbox should be in expected state.");
|
||||
|
||||
info("Test debug buttons disabled state.");
|
||||
let debugButtons = [...document.querySelectorAll("#addons .debug-button")];
|
||||
ok(debugButtons.every(b => b.disabled != testData.expected),
|
||||
"Debug buttons should be in the expected state");
|
||||
|
||||
info("Uninstall test addon installed earlier.");
|
||||
yield uninstallAddon(ADDON_ID);
|
||||
|
||||
yield closeAboutDebugging(tab);
|
||||
}
|
@ -13,6 +13,7 @@ add_task(function* () {
|
||||
yield new Promise(resolve => {
|
||||
let options = {"set": [
|
||||
["devtools.chrome.enabled", false],
|
||||
["devtools.debugger.remote-enabled", false],
|
||||
]};
|
||||
SpecialPowers.pushPrefEnv(options, resolve);
|
||||
});
|
||||
|
@ -9,6 +9,11 @@ Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
|
||||
this);
|
||||
|
||||
Services.prefs.setBoolPref("devtools.fontinspector.enabled", true);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.fontinspector.enabled");
|
||||
});
|
||||
|
||||
/**
|
||||
* Adds a new tab with the given URL, opens the inspector and selects the
|
||||
* font-inspector tab.
|
||||
|
@ -598,6 +598,7 @@ MarkupView.prototype = {
|
||||
*/
|
||||
_onKeyDown: function(event) {
|
||||
let handled = true;
|
||||
let previousNode, nextNode;
|
||||
|
||||
// Ignore keystrokes that originated in editors.
|
||||
if (this._isInputOrTextarea(event.target)) {
|
||||
@ -649,26 +650,26 @@ MarkupView.prototype = {
|
||||
}
|
||||
break;
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_UP:
|
||||
let prev = this._selectionWalker().previousNode();
|
||||
if (prev) {
|
||||
this.navigate(prev.container);
|
||||
previousNode = this._selectionWalker().previousNode();
|
||||
if (previousNode) {
|
||||
this.navigate(previousNode.container);
|
||||
}
|
||||
break;
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
|
||||
let next = this._selectionWalker().nextNode();
|
||||
if (next) {
|
||||
this.navigate(next.container);
|
||||
nextNode = this._selectionWalker().nextNode();
|
||||
if (nextNode) {
|
||||
this.navigate(nextNode.container);
|
||||
}
|
||||
break;
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP: {
|
||||
let walker = this._selectionWalker();
|
||||
let selection = this._selectedContainer;
|
||||
for (let i = 0; i < PAGE_SIZE; i++) {
|
||||
let prev = walker.previousNode();
|
||||
if (!prev) {
|
||||
previousNode = walker.previousNode();
|
||||
if (!previousNode) {
|
||||
break;
|
||||
}
|
||||
selection = prev.container;
|
||||
selection = previousNode.container;
|
||||
}
|
||||
this.navigate(selection);
|
||||
break;
|
||||
@ -677,11 +678,11 @@ MarkupView.prototype = {
|
||||
let walker = this._selectionWalker();
|
||||
let selection = this._selectedContainer;
|
||||
for (let i = 0; i < PAGE_SIZE; i++) {
|
||||
let next = walker.nextNode();
|
||||
if (!next) {
|
||||
nextNode = walker.nextNode();
|
||||
if (!nextNode) {
|
||||
break;
|
||||
}
|
||||
selection = next.container;
|
||||
selection = nextNode.container;
|
||||
}
|
||||
this.navigate(selection);
|
||||
break;
|
||||
@ -955,8 +956,8 @@ MarkupView.prototype = {
|
||||
|
||||
// If there has been additions, flash the nodes if their associated
|
||||
// container exist (so if their parent is expanded in the inspector).
|
||||
added.forEach(added => {
|
||||
let addedContainer = this.getContainer(added);
|
||||
added.forEach(node => {
|
||||
let addedContainer = this.getContainer(node);
|
||||
if (addedContainer) {
|
||||
addedOrEditedContainers.add(addedContainer);
|
||||
|
||||
@ -1501,55 +1502,56 @@ MarkupView.prototype = {
|
||||
// If the dirty flag is re-set while we're fetching we'll need to fetch
|
||||
// again.
|
||||
container.childrenDirty = false;
|
||||
let updatePromise = this._getVisibleChildren(container, centered).then(children => {
|
||||
if (!this._containers) {
|
||||
return promise.reject("markup view destroyed");
|
||||
}
|
||||
this._queuedChildUpdates.delete(container);
|
||||
let updatePromise =
|
||||
this._getVisibleChildren(container, centered).then(children => {
|
||||
if (!this._containers) {
|
||||
return promise.reject("markup view destroyed");
|
||||
}
|
||||
this._queuedChildUpdates.delete(container);
|
||||
|
||||
// If children are dirty, we got a change notification for this node
|
||||
// while the request was in progress, we need to do it again.
|
||||
if (container.childrenDirty) {
|
||||
return this._updateChildren(container, {expand: centered});
|
||||
}
|
||||
// If children are dirty, we got a change notification for this node
|
||||
// while the request was in progress, we need to do it again.
|
||||
if (container.childrenDirty) {
|
||||
return this._updateChildren(container, {expand: centered});
|
||||
}
|
||||
|
||||
let fragment = this.doc.createDocumentFragment();
|
||||
let fragment = this.doc.createDocumentFragment();
|
||||
|
||||
for (let child of children.nodes) {
|
||||
let container = this.importNode(child, flash);
|
||||
fragment.appendChild(container.elt);
|
||||
}
|
||||
for (let child of children.nodes) {
|
||||
let childContainer = this.importNode(child, flash);
|
||||
fragment.appendChild(childContainer.elt);
|
||||
}
|
||||
|
||||
while (container.children.firstChild) {
|
||||
container.children.removeChild(container.children.firstChild);
|
||||
}
|
||||
while (container.children.firstChild) {
|
||||
container.children.removeChild(container.children.firstChild);
|
||||
}
|
||||
|
||||
if (!(children.hasFirst && children.hasLast)) {
|
||||
let data = {
|
||||
showing: this.strings.GetStringFromName("markupView.more.showing"),
|
||||
showAll: this.strings.formatStringFromName(
|
||||
"markupView.more.showAll",
|
||||
[container.node.numChildren.toString()], 1),
|
||||
allButtonClick: () => {
|
||||
container.maxChildren = -1;
|
||||
container.childrenDirty = true;
|
||||
this._updateChildren(container);
|
||||
if (!(children.hasFirst && children.hasLast)) {
|
||||
let data = {
|
||||
showing: this.strings.GetStringFromName("markupView.more.showing"),
|
||||
showAll: this.strings.formatStringFromName(
|
||||
"markupView.more.showAll",
|
||||
[container.node.numChildren.toString()], 1),
|
||||
allButtonClick: () => {
|
||||
container.maxChildren = -1;
|
||||
container.childrenDirty = true;
|
||||
this._updateChildren(container);
|
||||
}
|
||||
};
|
||||
|
||||
if (!children.hasFirst) {
|
||||
let span = this.template("more-nodes", data);
|
||||
fragment.insertBefore(span, fragment.firstChild);
|
||||
}
|
||||
if (!children.hasLast) {
|
||||
let span = this.template("more-nodes", data);
|
||||
fragment.appendChild(span);
|
||||
}
|
||||
};
|
||||
|
||||
if (!children.hasFirst) {
|
||||
let span = this.template("more-nodes", data);
|
||||
fragment.insertBefore(span, fragment.firstChild);
|
||||
}
|
||||
if (!children.hasLast) {
|
||||
let span = this.template("more-nodes", data);
|
||||
fragment.appendChild(span);
|
||||
}
|
||||
}
|
||||
|
||||
container.children.appendChild(fragment);
|
||||
return container;
|
||||
}).then(null, console.error);
|
||||
container.children.appendChild(fragment);
|
||||
return container;
|
||||
}).then(null, console.error);
|
||||
this._queuedChildUpdates.set(container, updatePromise);
|
||||
return updatePromise;
|
||||
},
|
||||
@ -2867,8 +2869,8 @@ ElementEditor.prototype = {
|
||||
editor.input.select();
|
||||
}
|
||||
},
|
||||
done: (val, commit, direction) => {
|
||||
if (!commit || val === initial) {
|
||||
done: (newValue, commit, direction) => {
|
||||
if (!commit || newValue === initial) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2881,7 +2883,7 @@ ElementEditor.prototype = {
|
||||
this.refocusOnEdit(attribute.name, attr, direction);
|
||||
this._saveAttribute(attribute.name, undoMods);
|
||||
doMods.removeAttribute(attribute.name);
|
||||
this._applyAttributes(val, attr, doMods, undoMods);
|
||||
this._applyAttributes(newValue, attr, doMods, undoMods);
|
||||
this.container.undo.do(() => {
|
||||
doMods.apply();
|
||||
}, () => {
|
||||
@ -2906,8 +2908,9 @@ ElementEditor.prototype = {
|
||||
// it (make sure to pass a complete list of existing attributes to the
|
||||
// parseAttribute function, by concatenating attribute, because this could
|
||||
// be a newly added attribute not yet on this.node).
|
||||
let attributes = this.node.attributes
|
||||
.filter(({name}) => name !== attribute.name);
|
||||
let attributes = this.node.attributes.filter(existingAttribute => {
|
||||
return existingAttribute.name !== attribute.name;
|
||||
});
|
||||
attributes.push(attribute);
|
||||
let parsedLinksData = parseAttribute(this.node.namespaceURI,
|
||||
this.node.tagName, attributes, attribute.name);
|
||||
|
@ -216,6 +216,7 @@ devtools.jar:
|
||||
skin/styleeditor.css (themes/styleeditor.css)
|
||||
skin/webaudioeditor.css (themes/webaudioeditor.css)
|
||||
skin/components-frame.css (themes/components-frame.css)
|
||||
skin/components-h-split-box.css (themes/components-h-split-box.css)
|
||||
skin/jit-optimizations.css (themes/jit-optimizations.css)
|
||||
skin/images/magnifying-glass.png (themes/images/magnifying-glass.png)
|
||||
skin/images/magnifying-glass@2x.png (themes/images/magnifying-glass@2x.png)
|
||||
|
@ -7,7 +7,7 @@
|
||||
define(function(require, exports, module) {
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { Headers } = createFactories(require("./headers"));
|
||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
define(function(require, exports, module) {
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { TreeView } = createFactories(require("./reps/tree-view"));
|
||||
const { SearchBox } = createFactories(require("./search-box"));
|
||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||
|
@ -9,7 +9,7 @@
|
||||
define(function(require, exports, module) {
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { JsonPanel } = createFactories(require("./json-panel"));
|
||||
const { TextPanel } = createFactories(require("./text-panel"));
|
||||
const { HeadersPanel } = createFactories(require("./headers-panel"));
|
||||
|
@ -1,189 +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("./rep-utils");
|
||||
const { Rep } = createFactories(require("./rep"));
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
const { Caption } = createFactories(require("./caption"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders an array. The array is enclosed by left and right bracket
|
||||
* and the max number of rendered items depends on the current mode.
|
||||
*/
|
||||
var ArrayRep = React.createClass({
|
||||
displayName: "ArrayRep",
|
||||
|
||||
render: function() {
|
||||
var mode = this.props.mode || "short";
|
||||
var object = this.props.object;
|
||||
var hasTwisty = this.hasSpecialProperties(object);
|
||||
|
||||
var items;
|
||||
|
||||
if (mode == "tiny") {
|
||||
items = DOM.span({className: "length"}, object.length);
|
||||
} else {
|
||||
var max = (mode == "short") ? 3 : 300;
|
||||
items = this.arrayIterator(object, max);
|
||||
}
|
||||
|
||||
return (
|
||||
ObjectBox({className: "array", onClick: this.onToggleProperties},
|
||||
DOM.a({className: "objectLink", onclick: this.onClickBracket},
|
||||
DOM.span({className: "arrayLeftBracket", role: "presentation"}, "[")
|
||||
),
|
||||
items,
|
||||
DOM.a({className: "objectLink", onclick: this.onClickBracket},
|
||||
DOM.span({className: "arrayRightBracket", role: "presentation"}, "]")
|
||||
),
|
||||
DOM.span({className: "arrayProperties", role: "group"})
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
getTitle: function(object, context) {
|
||||
return "[" + object.length + "]";
|
||||
},
|
||||
|
||||
arrayIterator: function(array, max) {
|
||||
var items = [];
|
||||
|
||||
for (var i=0; i<array.length && i<=max; i++) {
|
||||
try {
|
||||
var delim = (i == array.length-1 ? "" : ", ");
|
||||
var value = array[i];
|
||||
|
||||
if (value === array) {
|
||||
items.push(Reference({
|
||||
key: i,
|
||||
object: value,
|
||||
delim: delim
|
||||
}));
|
||||
} else {
|
||||
items.push(ItemRep({
|
||||
key: i,
|
||||
object: value,
|
||||
delim: delim
|
||||
}));
|
||||
}
|
||||
} catch (exc) {
|
||||
items.push(ItemRep({object: exc, delim: delim, key: i}));
|
||||
}
|
||||
}
|
||||
|
||||
if (array.length > max + 1) {
|
||||
items.pop();
|
||||
items.push(Caption({
|
||||
key: "more",
|
||||
object: Locale.$STR("jsonViewer.reps.more"),
|
||||
}));
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the passed object is an array with additional (custom)
|
||||
* properties, otherwise returns false. Custom properties should be
|
||||
* displayed in extra expandable section.
|
||||
*
|
||||
* Example array with a custom property.
|
||||
* let arr = [0, 1];
|
||||
* arr.myProp = "Hello";
|
||||
*
|
||||
* @param {Array} array The array object.
|
||||
*/
|
||||
hasSpecialProperties: function(array) {
|
||||
function isInteger(x) {
|
||||
var y = parseInt(x, 10);
|
||||
if (isNaN(y)) {
|
||||
return false;
|
||||
}
|
||||
return x === y.toString();
|
||||
}
|
||||
|
||||
var n = 0;
|
||||
var props = Object.getOwnPropertyNames(array);
|
||||
for (var i=0; i<props.length; i++) {
|
||||
var p = props[i];
|
||||
|
||||
// Valid indexes are skipped
|
||||
if (isInteger(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore standard 'length' property, anything else is custom.
|
||||
if (p != "length") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
|
||||
onToggleProperties: function(event) {
|
||||
},
|
||||
|
||||
onClickBracket: function(event) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders array item. Individual values are separated by a comma.
|
||||
*/
|
||||
var ItemRep = React.createFactory(React.createClass({
|
||||
displayName: "ItemRep",
|
||||
|
||||
render: function(){
|
||||
var object = this.props.object;
|
||||
var delim = this.props.delim;
|
||||
return (
|
||||
DOM.span({},
|
||||
Rep({object: object}),
|
||||
delim
|
||||
)
|
||||
)
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* Renders cycle references in an array.
|
||||
*/
|
||||
var Reference = React.createFactory(React.createClass({
|
||||
displayName: "Reference",
|
||||
|
||||
render: function(){
|
||||
var tooltip = Locale.$STR("jsonView.reps.reference");
|
||||
return (
|
||||
span({title: tooltip},
|
||||
"[...]")
|
||||
)
|
||||
}
|
||||
}));
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return Array.isArray(object) ||
|
||||
Object.prototype.toString.call(object) === "[object Arguments]";
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.ArrayRep = {
|
||||
rep: ArrayRep,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
@ -1,31 +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 DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a caption. This template is used by other components
|
||||
* that needs to distinguish between a simple text/value and a label.
|
||||
*/
|
||||
const Caption = React.createClass({
|
||||
displayName: "Caption",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
DOM.span({"className": "caption"}, this.props.object)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.Caption = Caption;
|
||||
});
|
@ -5,18 +5,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'array.js',
|
||||
'caption.js',
|
||||
'null.js',
|
||||
'number.js',
|
||||
'object-box.js',
|
||||
'object-link.js',
|
||||
'object.js',
|
||||
'rep-utils.js',
|
||||
'rep.js',
|
||||
'string.js',
|
||||
'tabs.js',
|
||||
'toolbar.js',
|
||||
'tree-view.js',
|
||||
'undefined.js',
|
||||
)
|
||||
|
@ -1,46 +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("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders null value
|
||||
*/
|
||||
const Null = React.createClass({
|
||||
displayName: "NullRep",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
ObjectBox({className: "null"},
|
||||
"null"
|
||||
)
|
||||
)
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
if (object && object.type && object.type == "null") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (object == null);
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Null = {
|
||||
rep: Null,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
@ -1,47 +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("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders a number
|
||||
*/
|
||||
const Number = React.createClass({
|
||||
displayName: "Number",
|
||||
|
||||
render: function() {
|
||||
var value = this.props.object;
|
||||
return (
|
||||
ObjectBox({className: "number"},
|
||||
this.stringify(value)
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
stringify: function(object) {
|
||||
return (Object.is(object, -0) ? "-0" : String(object));
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return type == "boolean" || type == "number";
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Number = {
|
||||
rep: Number,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
@ -1,35 +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 DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a box for given object.
|
||||
*/
|
||||
const ObjectBox = React.createClass({
|
||||
displayName: "ObjectBox",
|
||||
|
||||
render: function() {
|
||||
var className = this.props.className;
|
||||
var boxClassName = className ? " objectBox-" + className : "";
|
||||
|
||||
return (
|
||||
DOM.span({className: "objectBox" + boxClassName, role: "presentation"},
|
||||
this.props.children
|
||||
)
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.ObjectBox = ObjectBox;
|
||||
});
|
@ -1,36 +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 DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a link for given object.
|
||||
*/
|
||||
const ObjectLink = React.createClass({
|
||||
displayName: "ObjectLink",
|
||||
|
||||
render: function() {
|
||||
var className = this.props.className;
|
||||
var objectClassName = className ? " objectLink-" + className : "";
|
||||
var linkClassName = "objectLink" + objectClassName + " a11yFocus";
|
||||
|
||||
return (
|
||||
DOM.a({className: linkClassName, _repObject: this.props.object},
|
||||
this.props.children
|
||||
)
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.ObjectLink = ObjectLink;
|
||||
});
|
@ -1,178 +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("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
const { Caption } = createFactories(require("./caption"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders an object. An object is represented by a list of its
|
||||
* properties enclosed in curly brackets.
|
||||
*/
|
||||
const Obj = React.createClass({
|
||||
displayName: "Obj",
|
||||
|
||||
render: function() {
|
||||
var object = this.props.object;
|
||||
var props = this.shortPropIterator(object);
|
||||
|
||||
return (
|
||||
ObjectBox({className: "object"},
|
||||
DOM.span({className: "objectTitle"}, this.getTitle(object)),
|
||||
DOM.span({className: "objectLeftBrace", role: "presentation"}, "{"),
|
||||
props,
|
||||
DOM.span({className: "objectRightBrace"}, "}")
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
getTitle: function() {
|
||||
return ""; // Could also be "Object";
|
||||
},
|
||||
|
||||
longPropIterator: function (object) {
|
||||
try {
|
||||
return this.propIterator(object, 100);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
|
||||
shortPropIterator: function (object) {
|
||||
try {
|
||||
return this.propIterator(object, /*could be a pref*/ 3);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
|
||||
propIterator: function(object, max) {
|
||||
function isInterestingProp(t, value) {
|
||||
return (t == "boolean" || t == "number" || (t == "string" && value) ||
|
||||
(t == "object" && value && value.toString));
|
||||
}
|
||||
|
||||
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
|
||||
if (Object.prototype.toString.call(object) === "[object Generator]") {
|
||||
object = Object.getPrototypeOf(object);
|
||||
}
|
||||
|
||||
// Object members with non-empty values are preferred since it gives the
|
||||
// user a better overview of the object.
|
||||
var props = [];
|
||||
this.getProps(props, object, max, isInterestingProp);
|
||||
|
||||
if (props.length <= max) {
|
||||
// There are not enough props yet (or at least, not enough props to
|
||||
// be able to know whether we should print "more..." or not).
|
||||
// Let's display also empty members and functions.
|
||||
this.getProps(props, object, max, function(t, value) {
|
||||
return !isInterestingProp(t, value);
|
||||
});
|
||||
}
|
||||
|
||||
if (props.length > max) {
|
||||
props.pop();
|
||||
props.push(Caption({
|
||||
key: "more",
|
||||
object: Locale.$STR("jsonViewer.reps.more"),
|
||||
}));
|
||||
}
|
||||
else if (props.length > 0) {
|
||||
// Remove the last comma.
|
||||
props[props.length-1] = React.cloneElement(
|
||||
props[props.length-1], { delim: "" });
|
||||
}
|
||||
|
||||
return props;
|
||||
},
|
||||
|
||||
getProps: function (props, object, max, filter) {
|
||||
max = max || 3;
|
||||
if (!object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var len = 0;
|
||||
var mode = this.props.mode;
|
||||
|
||||
try {
|
||||
for (var name in object) {
|
||||
if (props.length > max) {
|
||||
return;
|
||||
}
|
||||
|
||||
var value;
|
||||
try {
|
||||
value = object[name];
|
||||
}
|
||||
catch (exc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var t = typeof(value);
|
||||
if (filter(t, value)) {
|
||||
props.push(PropRep({
|
||||
key: name,
|
||||
mode: "short",
|
||||
name: name,
|
||||
object: value,
|
||||
equal: ": ",
|
||||
delim: ", ",
|
||||
mode: mode,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (exc) {
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders object property, name-value pair.
|
||||
*/
|
||||
var PropRep = React.createFactory(React.createClass({
|
||||
displayName: "PropRep",
|
||||
|
||||
render: function(){
|
||||
var { Rep } = createFactories(require("./rep"));
|
||||
var object = this.props.object;
|
||||
var mode = this.props.mode;
|
||||
return (
|
||||
DOM.span({},
|
||||
DOM.span({"className": "nodeName"}, this.props.name),
|
||||
DOM.span({"className": "objectEqual", role: "presentation"}, this.props.equal),
|
||||
Rep({object: object, mode: mode}),
|
||||
DOM.span({"className": "objectComma", role: "presentation"}, this.props.delim)
|
||||
)
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Obj = {
|
||||
rep: Obj,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
@ -1,29 +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");
|
||||
|
||||
/**
|
||||
* Create React factories for given arguments.
|
||||
* Example:
|
||||
* const { Rep } = createFactories(require("./rep"));
|
||||
*/
|
||||
function createFactories(args) {
|
||||
var result = {};
|
||||
for (var p in args) {
|
||||
result[p] = React.createFactory(args[p]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.createFactories = createFactories;
|
||||
});
|
@ -1,87 +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");
|
||||
|
||||
// Load all existing rep templates
|
||||
const { Undefined } = require("./undefined");
|
||||
const { Null } = require("./null");
|
||||
const { StringRep } = require("./string");
|
||||
const { Number } = require("./number");
|
||||
const { ArrayRep } = require("./array");
|
||||
const { Obj } = require("./object");
|
||||
|
||||
// List of all registered template.
|
||||
// XXX there should be a way for extensions to register a new
|
||||
// or modify an existing rep.
|
||||
var reps = [Undefined, Null, StringRep, Number, ArrayRep, Obj];
|
||||
var defaultRep;
|
||||
|
||||
/**
|
||||
* Generic rep that is using for rendering native JS types or an object.
|
||||
* The right template used for rendering is picked automatically according
|
||||
* to the current value type. The value must be passed is as 'object'
|
||||
* property.
|
||||
*/
|
||||
const Rep = React.createClass({
|
||||
displayName: "Rep",
|
||||
|
||||
render: function() {
|
||||
var rep = getRep(this.props.object);
|
||||
return rep(this.props);
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Return a rep object that is responsible for rendering given
|
||||
* object.
|
||||
*
|
||||
* @param object {Object} Object to be rendered in the UI. This
|
||||
* can be generic JS object as well as a grip (handle to a remote
|
||||
* debuggee object).
|
||||
*/
|
||||
function getRep(object) {
|
||||
var type = typeof(object);
|
||||
if (type == "object" && object instanceof String) {
|
||||
type = "string";
|
||||
}
|
||||
|
||||
if (isGrip(object)) {
|
||||
type = object.class;
|
||||
}
|
||||
|
||||
for (var i=0; i<reps.length; i++) {
|
||||
var rep = reps[i];
|
||||
try {
|
||||
// supportsObject could return weight (not only true/false
|
||||
// but a number), which would allow to priorities templates and
|
||||
// support better extensibility.
|
||||
if (rep.supportsObject(object, type)) {
|
||||
return React.createFactory(rep.rep);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error("reps.getRep; EXCEPTION ", err, err);
|
||||
}
|
||||
}
|
||||
|
||||
return React.createFactory(defaultRep.rep);
|
||||
}
|
||||
|
||||
function isGrip(object) {
|
||||
return object && object.actor;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.Rep = Rep;
|
||||
});
|
@ -1,102 +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("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders a string. String value is enclosed within quotes.
|
||||
*/
|
||||
const StringRep = React.createClass({
|
||||
displayName: "StringRep",
|
||||
|
||||
render: function() {
|
||||
var text = this.props.object;
|
||||
var member = this.props.member;
|
||||
if (member && member.open) {
|
||||
return (
|
||||
ObjectBox({className: "string"},
|
||||
"\"" + text + "\""
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
ObjectBox({className: "string"},
|
||||
"\"" + cropMultipleLines(text) + "\""
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
function escapeNewLines(value) {
|
||||
return value.replace(/\r/gm, "\\r").replace(/\n/gm, "\\n");
|
||||
};
|
||||
|
||||
function cropMultipleLines(text, limit) {
|
||||
return escapeNewLines(cropString(text, limit));
|
||||
};
|
||||
|
||||
function cropString(text, limit, alternativeText) {
|
||||
if (!alternativeText) {
|
||||
alternativeText = "...";
|
||||
}
|
||||
|
||||
// Make sure it's a string.
|
||||
text = text + "";
|
||||
|
||||
// Use default limit if necessary.
|
||||
if (!limit) {
|
||||
limit = 50;
|
||||
}
|
||||
|
||||
// Crop the string only if a limit is actually specified.
|
||||
if (limit <= 0) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Set the limit at least to the length of the alternative text
|
||||
// plus one character of the original text.
|
||||
if (limit <= alternativeText.length) {
|
||||
limit = alternativeText.length + 1;
|
||||
}
|
||||
|
||||
var halfLimit = (limit - alternativeText.length) / 2;
|
||||
|
||||
if (text.length > limit) {
|
||||
return text.substr(0, Math.ceil(halfLimit)) + alternativeText +
|
||||
text.substr(text.length - Math.floor(halfLimit));
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
function isCropped(value) {
|
||||
var cropLength = 50;
|
||||
return typeof(value) == "string" && value.length > cropLength;
|
||||
}
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return (type == "string");
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.StringRep = {
|
||||
rep: StringRep,
|
||||
supportsObject: supportsObject,
|
||||
isCropped: isCropped
|
||||
};
|
||||
|
||||
});
|
@ -8,9 +8,9 @@ define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { Rep } = createFactories(require("./rep"));
|
||||
const { StringRep } = require("./string");
|
||||
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;
|
||||
|
||||
var uid = 0;
|
||||
|
@ -1,46 +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("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders undefined value
|
||||
*/
|
||||
const Undefined = React.createClass({
|
||||
displayName: "UndefinedRep",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
ObjectBox({className: "undefined"},
|
||||
"undefined"
|
||||
)
|
||||
)
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
if (object && object.type && object.type == "undefined") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (type == "undefined");
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Undefined = {
|
||||
rep: Undefined,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
@ -7,7 +7,7 @@
|
||||
define(function(require, exports, module) {
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||
const DOM = React.DOM;
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
@import "resource://devtools/client/shared/components/reps/reps.css";
|
||||
|
||||
@import "general.css";
|
||||
@import "reps.css";
|
||||
@import "dom-tree.css";
|
||||
@import "search-box.css";
|
||||
@import "tabs.css";
|
||||
|
@ -14,7 +14,6 @@ DevToolsModules(
|
||||
'json-panel.css',
|
||||
'main.css',
|
||||
'read-only-prop.svg',
|
||||
'reps.css',
|
||||
'search-box.css',
|
||||
'search.svg',
|
||||
'tabs.css',
|
||||
|
@ -8,7 +8,7 @@ define(function(require, exports, module) {
|
||||
|
||||
// ReactJS
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const { createFactories } = require("./components/reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
|
||||
|
||||
const json = document.getElementById("json");
|
||||
|
@ -7,6 +7,7 @@ debug = Debug
|
||||
addons = Add-ons
|
||||
addonDebugging.label = Enable add-on debugging
|
||||
addonDebugging.tooltip = Turning this on will allow you to debug add-ons and various other parts of the browser chrome
|
||||
addonDebugging.moreInfo = more info
|
||||
loadTemporaryAddon = Load Temporary Add-on
|
||||
extensions = Extensions
|
||||
selectAddonFromFile = Select Add-on Directory or XPI File
|
||||
|
@ -368,3 +368,11 @@ heapview.field.name=Name
|
||||
# LOCALIZATION NOTE (heapview.field.name.tooltip): The tooltip for the column
|
||||
# header in the heap view for name.
|
||||
heapview.field.name.tooltip=The name of this group
|
||||
|
||||
# LOCALIZATION NOTE (shortest-paths.header): The header label for the shortest
|
||||
# paths pane.
|
||||
shortest-paths.header=Retaining Paths from GC Roots
|
||||
|
||||
# LOCALIZATION NOTE (shortest-paths.select-node): The message displayed in the
|
||||
# shortest paths pane when a node is not yet selected.
|
||||
shortest-paths.select-node=Select a node to view its retaining paths
|
||||
|
@ -12,6 +12,7 @@ DevToolsModules(
|
||||
'inverted.js',
|
||||
'io.js',
|
||||
'refresh.js',
|
||||
'sizes.js',
|
||||
'snapshot.js',
|
||||
'view.js',
|
||||
)
|
||||
|
13
devtools/client/memory/actions/sizes.js
Normal file
13
devtools/client/memory/actions/sizes.js
Normal file
@ -0,0 +1,13 @@
|
||||
/* 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 { actions } = require("../constants");
|
||||
|
||||
exports.resizeShortestPaths = function (newSize) {
|
||||
return {
|
||||
type: actions.RESIZE_SHORTEST_PATHS,
|
||||
size: newSize,
|
||||
};
|
||||
};
|
@ -33,6 +33,7 @@ const {
|
||||
focusDominatorTreeNode,
|
||||
} = require("./actions/snapshot");
|
||||
const { changeViewAndRefresh } = require("./actions/view");
|
||||
const { resizeShortestPaths } = require("./actions/sizes");
|
||||
const {
|
||||
breakdownNameToSpec,
|
||||
getBreakdownDisplayData,
|
||||
@ -113,7 +114,7 @@ const MemoryApp = createClass({
|
||||
filter,
|
||||
diffing,
|
||||
view,
|
||||
dominatorTreeBreakdown
|
||||
sizes,
|
||||
} = this.props;
|
||||
|
||||
const selectedSnapshot = snapshots.find(s => s.selected);
|
||||
@ -237,6 +238,10 @@ const MemoryApp = createClass({
|
||||
"...and that snapshot should have a dominator tree");
|
||||
dispatch(focusDominatorTreeNode(selectedSnapshot.id, node));
|
||||
},
|
||||
onShortestPathsResize: newSize => {
|
||||
dispatch(resizeShortestPaths(newSize));
|
||||
},
|
||||
sizes,
|
||||
view,
|
||||
})
|
||||
)
|
||||
|
@ -25,11 +25,11 @@ const CensusTreeItem = module.exports = createClass({
|
||||
depth,
|
||||
arrow,
|
||||
focused,
|
||||
toolbox,
|
||||
getPercentBytes,
|
||||
getPercentCount,
|
||||
showSign,
|
||||
onViewSourceInDebugger,
|
||||
inverted,
|
||||
} = this.props;
|
||||
|
||||
const bytes = formatNumber(item.bytes, showSign);
|
||||
@ -44,7 +44,14 @@ const CensusTreeItem = module.exports = createClass({
|
||||
const totalCount = formatNumber(item.totalCount, showSign);
|
||||
const percentTotalCount = formatPercent(getPercentCount(item.totalCount), showSign);
|
||||
|
||||
return dom.div({ className: `heap-tree-item ${focused ? "focused" :""}` },
|
||||
let pointer;
|
||||
if (inverted && depth > 0) {
|
||||
pointer = dom.span({ className: "children-pointer" }, "↖");
|
||||
} else if (!inverted && item.children && item.children.length) {
|
||||
pointer = dom.span({ className: "children-pointer" }, "↘");
|
||||
}
|
||||
|
||||
return dom.div({ className: `heap-tree-item ${focused ? "focused" : ""}` },
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" },
|
||||
dom.span({ className: "heap-tree-number" }, bytes),
|
||||
dom.span({ className: "heap-tree-percent" }, percentBytes)),
|
||||
@ -60,6 +67,7 @@ const CensusTreeItem = module.exports = createClass({
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-name",
|
||||
style: { marginLeft: depth * TREE_ROW_HEIGHT }},
|
||||
arrow,
|
||||
pointer,
|
||||
this.toLabel(item.name, onViewSourceInDebugger)
|
||||
)
|
||||
);
|
||||
|
@ -66,6 +66,7 @@ const Census = module.exports = createClass({
|
||||
getPercentBytes,
|
||||
getPercentCount,
|
||||
showSign: !!diffing,
|
||||
inverted: census.inverted,
|
||||
}),
|
||||
getRoots: () => report.children || [],
|
||||
getKey: node => node.id,
|
||||
|
@ -8,6 +8,8 @@ const Census = createFactory(require("./census"));
|
||||
const CensusHeader = createFactory(require("./census-header"));
|
||||
const DominatorTree = createFactory(require("./dominator-tree"));
|
||||
const DominatorTreeHeader = createFactory(require("./dominator-tree-header"));
|
||||
const HSplitBox = createFactory(require("devtools/client/shared/components/h-split-box"));
|
||||
const ShortestPaths = createFactory(require("./shortest-paths"));
|
||||
const { getStatusTextFull, L10N } = require("../utils");
|
||||
const { snapshotState: states, diffingState, viewState, dominatorTreeState } = require("../constants");
|
||||
const { snapshot: snapshotModel, diffingModel } = require("../models");
|
||||
@ -145,10 +147,12 @@ const Heap = module.exports = createClass({
|
||||
onDominatorTreeCollapse: PropTypes.func.isRequired,
|
||||
onCensusFocus: PropTypes.func.isRequired,
|
||||
onDominatorTreeFocus: PropTypes.func.isRequired,
|
||||
onShortestPathsResize: PropTypes.func.isRequired,
|
||||
snapshot: snapshotModel,
|
||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||
diffing: diffingModel,
|
||||
view: PropTypes.string.isRequired,
|
||||
sizes: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
render() {
|
||||
@ -277,8 +281,13 @@ const Heap = module.exports = createClass({
|
||||
},
|
||||
|
||||
_renderDominatorTree(state, onViewSourceInDebugger, dominatorTree, onLoadMoreSiblings) {
|
||||
return this._renderHeapView(
|
||||
state,
|
||||
const tree = dom.div(
|
||||
{
|
||||
className: "vbox",
|
||||
style: {
|
||||
overflowY: "auto"
|
||||
}
|
||||
},
|
||||
DominatorTreeHeader(),
|
||||
DominatorTree({
|
||||
onViewSourceInDebugger,
|
||||
@ -289,5 +298,21 @@ const Heap = module.exports = createClass({
|
||||
onFocus: this.props.onDominatorTreeFocus,
|
||||
})
|
||||
);
|
||||
|
||||
const shortestPaths = ShortestPaths({
|
||||
graph: dominatorTree.focused
|
||||
? dominatorTree.focused.shortestPaths
|
||||
: null
|
||||
});
|
||||
|
||||
return this._renderHeapView(
|
||||
state,
|
||||
HSplitBox({
|
||||
start: tree,
|
||||
end: shortestPaths,
|
||||
startWidth: this.props.sizes.shortestPathsSize,
|
||||
onResize: this.props.onShortestPathsResize,
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -12,6 +12,7 @@ DevToolsModules(
|
||||
'dominator-tree.js',
|
||||
'heap.js',
|
||||
'list.js',
|
||||
'shortest-paths.js',
|
||||
'snapshot-list-item.js',
|
||||
'toolbar.js',
|
||||
)
|
||||
|
189
devtools/client/memory/components/shortest-paths.js
Normal file
189
devtools/client/memory/components/shortest-paths.js
Normal file
@ -0,0 +1,189 @@
|
||||
/* 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 {
|
||||
DOM: dom,
|
||||
createClass,
|
||||
PropTypes,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
|
||||
const { getSourceNames } = require("devtools/client/shared/source-utils");
|
||||
const { L10N } = require("../utils");
|
||||
|
||||
const { ViewHelpers } = require("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
||||
const COMPONENTS_STRINGS_URI = "chrome://devtools/locale/components.properties";
|
||||
const componentsL10N = new ViewHelpers.L10N(COMPONENTS_STRINGS_URI);
|
||||
const UNKNOWN_SOURCE_STRING = componentsL10N.getStr("frame.unknownSource");
|
||||
|
||||
const GRAPH_DEFAULTS = {
|
||||
translate: [20, 20],
|
||||
scale: 1
|
||||
};
|
||||
|
||||
const NO_STACK = "noStack";
|
||||
const NO_FILENAME = "noFilename";
|
||||
const ROOT_LIST = "JS::ubi::RootList";
|
||||
|
||||
function stringifyLabel(label, id) {
|
||||
const sanitized = [];
|
||||
|
||||
for (let i = 0, length = label.length; i < length; i++) {
|
||||
const piece = label[i];
|
||||
|
||||
if (isSavedFrame(piece)) {
|
||||
const { short } = getSourceNames(piece.source, UNKNOWN_SOURCE_STRING);
|
||||
sanitized[i] = `${piece.functionDisplayName} @ ${short}:${piece.line}:${piece.column}`;
|
||||
} else if (piece === NO_STACK) {
|
||||
sanitized[i] = L10N.getStr("tree-item.nostack");
|
||||
} else if (piece === NO_FILENAME) {
|
||||
sanitized[i] = L10N.getStr("tree-item.nofilename");
|
||||
} else if (piece === ROOT_LIST) {
|
||||
// Don't use the usual labeling machinery for root lists: replace it
|
||||
// with the "GC Roots" string.
|
||||
sanitized.splice(0, label.length);
|
||||
sanitized.push(L10N.getStr("tree-item.rootlist"));
|
||||
break;
|
||||
} else {
|
||||
sanitized[i] = "" + piece;
|
||||
}
|
||||
}
|
||||
|
||||
return `${sanitized.join(" › ")} @ 0x${id.toString(16)}`;
|
||||
}
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "ShortestPaths",
|
||||
|
||||
propTypes: {
|
||||
graph: PropTypes.shape({
|
||||
nodes: PropTypes.arrayOf(PropTypes.object),
|
||||
edges: PropTypes.arrayOf(PropTypes.object),
|
||||
}),
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return { zoom: null };
|
||||
},
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return this.props.graph != nextProps.graph;
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.graph) {
|
||||
this._renderGraph(this.refs.container, this.props.graph);
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.graph) {
|
||||
this._renderGraph(this.refs.container, this.props.graph);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.state.zoom) {
|
||||
this.state.zoom.on("zoom", null);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
let contents;
|
||||
if (this.props.graph) {
|
||||
// Let the componentDidMount or componentDidUpdate method draw the graph
|
||||
// with DagreD3. We just provide the container for the graph here.
|
||||
contents = dom.div({
|
||||
ref: "container",
|
||||
style: {
|
||||
flex: 1,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
}
|
||||
});
|
||||
} else {
|
||||
contents = dom.div(
|
||||
{
|
||||
id: "shortest-paths-select-node-msg"
|
||||
},
|
||||
L10N.getStr("shortest-paths.select-node")
|
||||
);
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
id: "shortest-paths",
|
||||
className: "vbox",
|
||||
},
|
||||
dom.label(
|
||||
{
|
||||
id: "shortest-paths-header",
|
||||
className: "header",
|
||||
},
|
||||
L10N.getStr("shortest-paths.header")
|
||||
),
|
||||
contents
|
||||
);
|
||||
},
|
||||
|
||||
_renderGraph(container, { nodes, edges }) {
|
||||
if (!container.firstChild) {
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("id", "graph-svg");
|
||||
svg.setAttribute("xlink", "http://www.w3.org/1999/xlink");
|
||||
svg.style.width = "100%";
|
||||
svg.style.height = "100%";
|
||||
|
||||
const target = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
||||
target.setAttribute("id", "graph-target");
|
||||
target.style.width = "100%";
|
||||
target.style.height = "100%";
|
||||
|
||||
svg.appendChild(target);
|
||||
container.appendChild(svg);
|
||||
}
|
||||
|
||||
const graph = new dagreD3.Digraph();
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
graph.addNode(nodes[i].id, {
|
||||
id: nodes[i].id,
|
||||
label: stringifyLabel(nodes[i].label, nodes[i].id),
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
graph.addEdge(null, edges[i].from, edges[i].to, {
|
||||
label: edges[i].name
|
||||
});
|
||||
}
|
||||
|
||||
const renderer = new dagreD3.Renderer();
|
||||
renderer.drawNodes();
|
||||
renderer.drawEdgePaths();
|
||||
|
||||
const svg = d3.select("#graph-svg");
|
||||
const target = d3.select("#graph-target");
|
||||
|
||||
let zoom = this.state.zoom;
|
||||
if (!zoom) {
|
||||
zoom = d3.behavior.zoom().on("zoom", function() {
|
||||
target.attr(
|
||||
"transform",
|
||||
`translate(${d3.event.translate}) scale(${d3.event.scale})`
|
||||
);
|
||||
});
|
||||
svg.call(zoom);
|
||||
this.setState({ zoom });
|
||||
}
|
||||
|
||||
const { translate, scale } = GRAPH_DEFAULTS;
|
||||
zoom.scale(scale);
|
||||
zoom.translate(translate);
|
||||
target.attr("transform", `translate(${translate}) scale(${scale})`);
|
||||
|
||||
const layout = dagreD3.layout();
|
||||
renderer.layout(layout).run(graph, target);
|
||||
},
|
||||
});
|
@ -101,6 +101,8 @@ actions.FETCH_IMMEDIATELY_DOMINATED_END = "fetch-immediately-dominated-end";
|
||||
actions.EXPAND_DOMINATOR_TREE_NODE = "expand-dominator-tree-node";
|
||||
actions.COLLAPSE_DOMINATOR_TREE_NODE = "collapse-dominator-tree-node";
|
||||
|
||||
actions.RESIZE_SHORTEST_PATHS = "resize-shortest-paths";
|
||||
|
||||
/*** Breakdowns ***************************************************************/
|
||||
|
||||
const COUNT = { by: "count", count: true, bytes: true };
|
||||
|
@ -14,14 +14,29 @@
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/widgets.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/memory.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/components-frame.css" type="text/css"/>
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="initializer.js"></script>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/components-h-split-box.css" type="text/css"/>
|
||||
</head>
|
||||
<body class="theme-body">
|
||||
<div id="app">
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"
|
||||
defer="true">
|
||||
</script>
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="initializer.js"
|
||||
defer="true">
|
||||
</script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/d3.js"
|
||||
defer="true">
|
||||
</script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/dagre-d3.js"
|
||||
defer="true">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -10,5 +10,6 @@ exports.dominatorTreeBreakdown = require("./reducers/dominatorTreeBreakdown");
|
||||
exports.errors = require("./reducers/errors");
|
||||
exports.filter = require("./reducers/filter");
|
||||
exports.inverted = require("./reducers/inverted");
|
||||
exports.sizes = require("./reducers/sizes");
|
||||
exports.snapshots = require("./reducers/snapshots");
|
||||
exports.view = require("./reducers/view");
|
||||
|
@ -11,6 +11,7 @@ DevToolsModules(
|
||||
'errors.js',
|
||||
'filter.js',
|
||||
'inverted.js',
|
||||
'sizes.js',
|
||||
'snapshots.js',
|
||||
'view.js',
|
||||
)
|
||||
|
18
devtools/client/memory/reducers/sizes.js
Normal file
18
devtools/client/memory/reducers/sizes.js
Normal file
@ -0,0 +1,18 @@
|
||||
/* 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 { actions } = require("../constants");
|
||||
const { immutableUpdate } = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
const handlers = Object.create(null);
|
||||
|
||||
handlers[actions.RESIZE_SHORTEST_PATHS] = function (sizes, { size }) {
|
||||
return immutableUpdate(sizes, { shortestPathsSize: size });
|
||||
};
|
||||
|
||||
module.exports = function (sizes = { shortestPathsSize: .5 }, action) {
|
||||
const handler = handlers[action.type];
|
||||
return handler ? handler(sizes, action) : sizes;
|
||||
};
|
@ -26,10 +26,19 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
|
||||
|
||||
const nameElems = [...doc.querySelectorAll(".heap-tree-item-field.heap-tree-item-name")];
|
||||
is(nameElems.length, 4, "Should get 4 items, one for each coarse type");
|
||||
ok(nameElems.some(e => e.textContent.trim() === "objects"), "One for coarse type 'objects'");
|
||||
ok(nameElems.some(e => e.textContent.trim() === "scripts"), "One for coarse type 'scripts'");
|
||||
ok(nameElems.some(e => e.textContent.trim() === "strings"), "One for coarse type 'strings'");
|
||||
ok(nameElems.some(e => e.textContent.trim() === "other"), "One for coarse type 'other'");
|
||||
|
||||
for (let el of nameElems) {
|
||||
dumpn(`Found ${el.textContent.trim()}`);
|
||||
}
|
||||
|
||||
ok(nameElems.some(e => e.textContent.indexOf("objects") >= 0),
|
||||
"One for coarse type 'objects'");
|
||||
ok(nameElems.some(e => e.textContent.indexOf("scripts") >= 0),
|
||||
"One for coarse type 'scripts'");
|
||||
ok(nameElems.some(e => e.textContent.indexOf("strings") >= 0),
|
||||
"One for coarse type 'strings'");
|
||||
ok(nameElems.some(e => e.textContent.indexOf("other") >= 0),
|
||||
"One for coarse type 'other'");
|
||||
|
||||
for (let e of nameElems) {
|
||||
is(e.style.marginLeft, "0px",
|
||||
|
@ -72,6 +72,10 @@ function makeMemoryTest(url, generator) {
|
||||
});
|
||||
}
|
||||
|
||||
function dumpn(msg) {
|
||||
dump(`MEMORY-TEST: ${msg}\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that will resolve when the provided store matches
|
||||
* the expected array. expectedStates is an array of dominatorTree states.
|
||||
|
@ -2,6 +2,7 @@
|
||||
support-files =
|
||||
head.js
|
||||
|
||||
[test_CensusTreeItem_01.html]
|
||||
[test_DominatorTree_01.html]
|
||||
[test_DominatorTree_02.html]
|
||||
[test_DominatorTree_03.html]
|
||||
@ -10,5 +11,7 @@ support-files =
|
||||
[test_Heap_02.html]
|
||||
[test_Heap_03.html]
|
||||
[test_Heap_04.html]
|
||||
[test_ShortestPaths_01.html]
|
||||
[test_ShortestPaths_02.html]
|
||||
[test_Toolbar_01.html]
|
||||
[test_Toolbar_02.html]
|
||||
|
@ -28,6 +28,7 @@ var {
|
||||
const {
|
||||
getBreakdownDisplayData,
|
||||
getDominatorTreeBreakdownDisplayData,
|
||||
L10N,
|
||||
} = require("devtools/client/memory/utils");
|
||||
|
||||
var models = require("devtools/client/memory/models");
|
||||
@ -35,8 +36,10 @@ var models = require("devtools/client/memory/models");
|
||||
var React = require("devtools/client/shared/vendor/react");
|
||||
var ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
var Heap = React.createFactory(require("devtools/client/memory/components/heap"));
|
||||
var CensusTreeItem = React.createFactory(require("devtools/client/memory/components/census-tree-item"));
|
||||
var DominatorTreeComponent = React.createFactory(require("devtools/client/memory/components/dominator-tree"));
|
||||
var DominatorTreeItem = React.createFactory(require("devtools/client/memory/components/dominator-tree-item"));
|
||||
var ShortestPaths = React.createFactory(require("devtools/client/memory/components/shortest-paths"));
|
||||
var Toolbar = React.createFactory(require("devtools/client/memory/components/toolbar"));
|
||||
|
||||
// All tests are asynchronous.
|
||||
@ -44,6 +47,33 @@ SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var noop = () => {};
|
||||
|
||||
var TEST_CENSUS_TREE_ITEM_PROPS = Object.freeze({
|
||||
item: Object.freeze({
|
||||
bytes: 10,
|
||||
count: 1,
|
||||
totalBytes: 10,
|
||||
totalCount: 1,
|
||||
name: "foo",
|
||||
children: [
|
||||
Object.freeze({
|
||||
bytes: 10,
|
||||
count: 1,
|
||||
totalBytes: 10,
|
||||
totalCount: 1,
|
||||
name: "bar",
|
||||
})
|
||||
]
|
||||
}),
|
||||
depth: 0,
|
||||
arrow: ">",
|
||||
focused: true,
|
||||
getPercentBytes: () => 50,
|
||||
getPercentCount: () => 50,
|
||||
showSign: false,
|
||||
onViewSourceInDebugger: noop,
|
||||
inverted: false,
|
||||
});
|
||||
|
||||
// Counter for mock DominatorTreeNode ids.
|
||||
var TEST_NODE_ID_COUNTER = 0;
|
||||
|
||||
@ -105,6 +135,21 @@ var TEST_DOMINATOR_TREE_PROPS = Object.freeze({
|
||||
onCollapse: noop,
|
||||
});
|
||||
|
||||
var TEST_SHORTEST_PATHS_PROPS = Object.freeze({
|
||||
graph: Object.freeze({
|
||||
nodes: [
|
||||
{ id: 1, label: ["other", "SomeType"] },
|
||||
{ id: 2, label: ["other", "SomeType"] },
|
||||
{ id: 3, label: ["other", "SomeType"] },
|
||||
],
|
||||
edges: [
|
||||
{ from: 1, to: 2, name: "1->2" },
|
||||
{ from: 1, to: 3, name: "1->3" },
|
||||
{ from: 2, to: 3, name: "2->3" },
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
var TEST_HEAP_PROPS = Object.freeze({
|
||||
onSnapshotClick: noop,
|
||||
onLoadMoreSiblings: noop,
|
||||
@ -146,6 +191,8 @@ var TEST_HEAP_PROPS = Object.freeze({
|
||||
creationTime: 0,
|
||||
state: snapshotState.SAVED_CENSUS,
|
||||
}),
|
||||
sizes: Object.freeze({ shortestPathsSize: .5 }),
|
||||
onShortestPathsResize: noop,
|
||||
});
|
||||
|
||||
var TEST_TOOLBAR_PROPS = Object.freeze({
|
||||
|
@ -0,0 +1,65 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test that children pointers show up at the correct times.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tree component test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Give the container height so that the whole tree is rendered. -->
|
||||
<div id="container" style="height: 900px;"></div>
|
||||
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
const container = document.getElementById("container");
|
||||
|
||||
yield renderComponent(CensusTreeItem(immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS, {
|
||||
inverted: true,
|
||||
depth: 0,
|
||||
})), container);
|
||||
|
||||
ok(!container.querySelector(".children-pointer"),
|
||||
"Don't show children pointer for roots when we are inverted");
|
||||
|
||||
yield renderComponent(CensusTreeItem(immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS, {
|
||||
inverted: true,
|
||||
depth: 1,
|
||||
})), container);
|
||||
|
||||
ok(container.querySelector(".children-pointer"),
|
||||
"Do show children pointer for non-roots when we are inverted");
|
||||
|
||||
yield renderComponent(CensusTreeItem(immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS, {
|
||||
inverted: false,
|
||||
item: immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS.item, { children: undefined }),
|
||||
})), container);
|
||||
|
||||
ok(!container.querySelector(".children-pointer"),
|
||||
"Don't show children pointer when non-inverted and no children");
|
||||
|
||||
yield renderComponent(CensusTreeItem(immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS, {
|
||||
inverted: false,
|
||||
depth: 0,
|
||||
item: immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS.item, { children: [{}] }),
|
||||
})), container);
|
||||
|
||||
ok(container.querySelector(".children-pointer"),
|
||||
"Do show children pointer when non-inverted and have children");
|
||||
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
112
devtools/client/memory/test/chrome/test_ShortestPaths_01.html
Normal file
112
devtools/client/memory/test/chrome/test_ShortestPaths_01.html
Normal file
@ -0,0 +1,112 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test that the ShortestPaths component properly renders a graph of the merged shortest paths.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tree component test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/d3.js">
|
||||
</script>
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/dagre-d3.js">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Give the container height so that the whole tree is rendered. -->
|
||||
<div id="container" style="height: 900px;"></div>
|
||||
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
const container = document.getElementById("container");
|
||||
|
||||
yield renderComponent(ShortestPaths(TEST_SHORTEST_PATHS_PROPS), container);
|
||||
|
||||
let found1 = false;
|
||||
let found2 = false;
|
||||
let found3 = false;
|
||||
|
||||
let found1to2 = false;
|
||||
let found1to3 = false;
|
||||
let found2to3 = false;
|
||||
|
||||
const tspans = [...container.querySelectorAll("tspan")];
|
||||
for (let el of tspans) {
|
||||
const text = el.textContent.trim();
|
||||
dumpn("tspan's text = " + text);
|
||||
|
||||
switch (text) {
|
||||
// Nodes
|
||||
|
||||
case "other › SomeType @ 0x1": {
|
||||
ok(!found1, "Should only find node 1 once");
|
||||
found1 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "other › SomeType @ 0x2": {
|
||||
ok(!found2, "Should only find node 2 once");
|
||||
found2 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "other › SomeType @ 0x3": {
|
||||
ok(!found3, "Should only find node 3 once");
|
||||
found3 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Edges
|
||||
|
||||
case "1->2": {
|
||||
ok(!found1to2, "Should only find edge 1->2 once");
|
||||
found1to2 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "1->3": {
|
||||
ok(!found1to3, "Should only find edge 1->3 once");
|
||||
found1to3 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "2->3": {
|
||||
ok(!found2to3, "Should only find edge 2->3 once");
|
||||
found2to3 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Unexpected
|
||||
|
||||
default: {
|
||||
ok(false, `Unexpected tspan: ${text}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ok(found1, "Should have rendered node 1");
|
||||
ok(found2, "Should have rendered node 2");
|
||||
ok(found3, "Should have rendered node 3");
|
||||
|
||||
ok(found1to2, "Should have rendered edge 1->2");
|
||||
ok(found1to3, "Should have rendered edge 1->3");
|
||||
ok(found2to3, "Should have rendered edge 2->3");
|
||||
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test that the ShortestPaths component renders a suggestion to select a node when there is no graph.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tree component test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/d3.js">
|
||||
</script>
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/dagre-d3.js">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Give the container height so that the whole tree is rendered. -->
|
||||
<div id="container" style="height: 900px;"></div>
|
||||
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
const container = document.getElementById("container");
|
||||
|
||||
yield renderComponent(ShortestPaths(immutableUpdate(TEST_SHORTEST_PATHS_PROPS,
|
||||
{ graph: null })),
|
||||
container);
|
||||
|
||||
ok(container.textContent.indexOf(L10N.getStr("shortest-paths.select-node")) !== -1,
|
||||
"The node selection prompt is displayed");
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -212,8 +212,17 @@ const DOM = {
|
||||
container.className = "marker-details-stack";
|
||||
container.appendChild(labelName);
|
||||
|
||||
// Workaround for profiles that have looping stack traces. See
|
||||
// bug 1246555.
|
||||
let wasAsyncParent = false;
|
||||
let seen = new Set();
|
||||
|
||||
while (frameIndex > 0) {
|
||||
if (seen.has(frameIndex)) {
|
||||
break;
|
||||
}
|
||||
seen.add(frameIndex);
|
||||
|
||||
let frame = frames[frameIndex];
|
||||
let url = frame.source;
|
||||
let displayName = frame.functionDisplayName;
|
||||
|
@ -58,9 +58,21 @@ function* spawnTest() {
|
||||
return m.start;
|
||||
}, 0);
|
||||
|
||||
// Override the timestamp marker's stack with our own recursive stack, which
|
||||
// can happen for unknown reasons (bug 1246555); we should not cause a crash
|
||||
// when attempting to render a recursive stack trace
|
||||
let timestampMarker = markers.find(m => m.name === "ConsoleTime");
|
||||
ok(typeof timestampMarker.stack === "number", "ConsoleTime marker has a stack before overwriting.");
|
||||
let frames = PerformanceController.getCurrentRecording().getFrames();
|
||||
let frameIndex = timestampMarker.stack = frames.length;
|
||||
frames.push({ line: 1, column: 1, source: "file.js", functionDisplayName: "test", parent: frameIndex + 1});
|
||||
frames.push({ line: 1, column: 1, source: "file.js", functionDisplayName: "test", parent: frameIndex + 2 });
|
||||
frames.push({ line: 1, column: 1, source: "file.js", functionDisplayName: "test", parent: frameIndex });
|
||||
|
||||
const tests = {
|
||||
ConsoleTime: function (marker) {
|
||||
info("Got `ConsoleTime` marker with data: " + JSON.stringify(marker));
|
||||
ok(marker.stack === frameIndex, "Should have the ConsoleTime marker with recursive stack");
|
||||
shouldHaveStack($, "startStack", marker);
|
||||
shouldHaveStack($, "endStack", marker);
|
||||
shouldHaveLabel($, "Timer Name:", "!!!", marker);
|
||||
|
@ -325,7 +325,7 @@ pref("devtools.editor.enableCodeFolding", true);
|
||||
pref("devtools.editor.autocomplete", true);
|
||||
|
||||
// Enable the Font Inspector
|
||||
pref("devtools.fontinspector.enabled", true);
|
||||
pref("devtools.fontinspector.enabled", false);
|
||||
|
||||
// Pref to store the browser version at the time of a telemetry ping for an
|
||||
// opened developer tool. This allows us to ping telemetry just once per browser
|
||||
|
5
devtools/client/shared/components/.eslintrc
Normal file
5
devtools/client/shared/components/.eslintrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"globals": {
|
||||
"define": true,
|
||||
}
|
||||
}
|
138
devtools/client/shared/components/h-split-box.js
Normal file
138
devtools/client/shared/components/h-split-box.js
Normal file
@ -0,0 +1,138 @@
|
||||
/* 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/. */
|
||||
|
||||
// A box with a start and a end pane, separated by a dragable splitter that
|
||||
// allows the user to resize the relative widths of the panes.
|
||||
//
|
||||
// +-----------------------+---------------------+
|
||||
// | | |
|
||||
// | | |
|
||||
// | S |
|
||||
// | Start Pane p End Pane |
|
||||
// | l |
|
||||
// | i |
|
||||
// | t |
|
||||
// | t |
|
||||
// | e |
|
||||
// | r |
|
||||
// | | |
|
||||
// | | |
|
||||
// +-----------------------+---------------------+
|
||||
|
||||
const {
|
||||
DOM: dom,
|
||||
createClass,
|
||||
PropTypes,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "HSplitBox",
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
startWidth: 0.5,
|
||||
minStartWidth: "20px",
|
||||
minEndWidth: "20px",
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
mouseDown: false
|
||||
};
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
// The contents of the start pane.
|
||||
start: PropTypes.any.isRequired,
|
||||
|
||||
// The contents of the end pane.
|
||||
end: PropTypes.any.isRequired,
|
||||
|
||||
// The relative width of the start pane, expressed as a number between 0 and
|
||||
// 1. The relative width of the end pane is 1 - startWidth. For example,
|
||||
// with startWidth = .5, both panes are of equal width; with startWidth =
|
||||
// .25, the start panel will take up 1/4 width and the end panel will take
|
||||
// up 3/4 width.
|
||||
startWidth: PropTypes.number,
|
||||
|
||||
// A minimum css width value for the start and end panes.
|
||||
minStartWidth: PropTypes.any,
|
||||
minEndWidth: PropTypes.any,
|
||||
|
||||
// A callback fired when the user drags the splitter to resize the relative
|
||||
// pane widths. The function is passed the startWidth value that would put
|
||||
// the splitter underneath the users mouse.
|
||||
onResize: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
_onMouseDown(event) {
|
||||
this.setState({ mouseDown: true });
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_onMouseUp(event) {
|
||||
this.setState({ mouseDown: false });
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_onMouseMove(event) {
|
||||
if (!this.state.mouseDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = this.refs.box.getBoundingClientRect();
|
||||
const { left, right } = rect;
|
||||
const width = right - left;
|
||||
const relative = event.clientX - left;
|
||||
this.props.onResize(relative / width);
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
document.defaultView.top.addEventListener("mouseup", this._onMouseUp, false);
|
||||
document.defaultView.top.addEventListener("mousemove", this._onMouseMove, false);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
document.defaultView.top.removeEventListener("mouseup", this._onMouseUp, false);
|
||||
document.defaultView.top.removeEventListener("mousemove", this._onMouseMove, false);
|
||||
},
|
||||
|
||||
render() {
|
||||
const { start, end, startWidth, minStartWidth, minEndWidth } = this.props;
|
||||
assert(0 <= startWidth && startWidth <= 1,
|
||||
"0 <= this.props.startWidth <= 1");
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "h-split-box",
|
||||
ref: "box",
|
||||
},
|
||||
|
||||
dom.div(
|
||||
{
|
||||
className: "h-split-box-pane",
|
||||
style: { flex: startWidth, minWidth: minStartWidth },
|
||||
},
|
||||
start
|
||||
),
|
||||
|
||||
dom.div({
|
||||
className: "h-split-box-splitter",
|
||||
onMouseDown: this._onMouseDown,
|
||||
}),
|
||||
|
||||
dom.div(
|
||||
{
|
||||
className: "h-split-box-pane",
|
||||
style: { flex: 1 - startWidth, minWidth: minEndWidth },
|
||||
},
|
||||
end
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
@ -4,8 +4,13 @@
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'reps',
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'frame.js',
|
||||
'h-split-box.js',
|
||||
'tree.js',
|
||||
)
|
||||
|
||||
|
208
devtools/client/shared/components/reps/array.js
Normal file
208
devtools/client/shared/components/reps/array.js
Normal file
@ -0,0 +1,208 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { Rep } = createFactories(require("./rep"));
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
const { Caption } = createFactories(require("./caption"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders an array. The array is enclosed by left and right bracket
|
||||
* and the max number of rendered items depends on the current mode.
|
||||
*/
|
||||
let ArrayRep = React.createClass({
|
||||
displayName: "ArrayRep",
|
||||
|
||||
render: function() {
|
||||
let mode = this.props.mode || "short";
|
||||
let object = this.props.object;
|
||||
let items;
|
||||
|
||||
if (mode == "tiny") {
|
||||
items = DOM.span({className: "length"}, object.length);
|
||||
} else {
|
||||
let max = (mode == "short") ? 3 : 300;
|
||||
items = this.arrayIterator(object, max);
|
||||
}
|
||||
|
||||
return (
|
||||
ObjectBox({
|
||||
className: "array",
|
||||
onClick: this.onToggleProperties},
|
||||
DOM.a({
|
||||
className: "objectLink",
|
||||
onclick: this.onClickBracket},
|
||||
DOM.span({
|
||||
className: "arrayLeftBracket",
|
||||
role: "presentation"},
|
||||
"["
|
||||
)
|
||||
),
|
||||
items,
|
||||
DOM.a({
|
||||
className: "objectLink",
|
||||
onclick: this.onClickBracket},
|
||||
DOM.span({
|
||||
className: "arrayRightBracket",
|
||||
role: "presentation"},
|
||||
"]"
|
||||
)
|
||||
),
|
||||
DOM.span({
|
||||
className: "arrayProperties",
|
||||
role: "group"}
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
getTitle: function(object, context) {
|
||||
return "[" + object.length + "]";
|
||||
},
|
||||
|
||||
arrayIterator: function(array, max) {
|
||||
let items = [];
|
||||
let delim;
|
||||
|
||||
for (let i = 0; i < array.length && i <= max; i++) {
|
||||
try {
|
||||
let value = array[i];
|
||||
|
||||
delim = (i == array.length - 1 ? "" : ", ");
|
||||
|
||||
if (value === array) {
|
||||
items.push(Reference({
|
||||
key: i,
|
||||
object: value,
|
||||
delim: delim
|
||||
}));
|
||||
} else {
|
||||
items.push(ItemRep({
|
||||
key: i,
|
||||
object: value,
|
||||
delim: delim
|
||||
}));
|
||||
}
|
||||
} catch (exc) {
|
||||
items.push(ItemRep({
|
||||
object: exc,
|
||||
delim: delim,
|
||||
key: i
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (array.length > max + 1) {
|
||||
items.pop();
|
||||
items.push(Caption({
|
||||
key: "more",
|
||||
object: "more...",
|
||||
}));
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the passed object is an array with additional (custom)
|
||||
* properties, otherwise returns false. Custom properties should be
|
||||
* displayed in extra expandable section.
|
||||
*
|
||||
* Example array with a custom property.
|
||||
* let arr = [0, 1];
|
||||
* arr.myProp = "Hello";
|
||||
*
|
||||
* @param {Array} array The array object.
|
||||
*/
|
||||
hasSpecialProperties: function(array) {
|
||||
function isInteger(x) {
|
||||
let y = parseInt(x, 10);
|
||||
if (isNaN(y)) {
|
||||
return false;
|
||||
}
|
||||
return x === y.toString();
|
||||
}
|
||||
|
||||
let props = Object.getOwnPropertyNames(array);
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
let p = props[i];
|
||||
|
||||
// Valid indexes are skipped
|
||||
if (isInteger(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore standard 'length' property, anything else is custom.
|
||||
if (p != "length") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
|
||||
onToggleProperties: function(event) {
|
||||
},
|
||||
|
||||
onClickBracket: function(event) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders array item. Individual values are separated by a comma.
|
||||
*/
|
||||
let ItemRep = React.createFactory(React.createClass({
|
||||
displayName: "ItemRep",
|
||||
|
||||
render: function() {
|
||||
let object = this.props.object;
|
||||
let delim = this.props.delim;
|
||||
return (
|
||||
DOM.span({},
|
||||
Rep({object: object}),
|
||||
delim
|
||||
)
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* Renders cycle references in an array.
|
||||
*/
|
||||
let Reference = React.createFactory(React.createClass({
|
||||
displayName: "Reference",
|
||||
|
||||
render: function() {
|
||||
let tooltip = "Circular reference";
|
||||
return (
|
||||
DOM.span({title: tooltip},
|
||||
"[...]")
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return Array.isArray(object) ||
|
||||
Object.prototype.toString.call(object) === "[object Arguments]";
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.ArrayRep = {
|
||||
rep: ArrayRep,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
31
devtools/client/shared/components/reps/caption.js
Normal file
31
devtools/client/shared/components/reps/caption.js
Normal file
@ -0,0 +1,31 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a caption. This template is used by other components
|
||||
* that needs to distinguish between a simple text/value and a label.
|
||||
*/
|
||||
const Caption = React.createClass({
|
||||
displayName: "Caption",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
DOM.span({"className": "caption"}, this.props.object)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.Caption = Caption;
|
||||
});
|
20
devtools/client/shared/components/reps/moz.build
Normal file
20
devtools/client/shared/components/reps/moz.build
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'array.js',
|
||||
'caption.js',
|
||||
'null.js',
|
||||
'number.js',
|
||||
'object-box.js',
|
||||
'object-link.js',
|
||||
'object.js',
|
||||
'rep-utils.js',
|
||||
'rep.js',
|
||||
'reps.css',
|
||||
'string.js',
|
||||
'undefined.js',
|
||||
)
|
45
devtools/client/shared/components/reps/null.js
Normal file
45
devtools/client/shared/components/reps/null.js
Normal file
@ -0,0 +1,45 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders null value
|
||||
*/
|
||||
const Null = React.createClass({
|
||||
displayName: "NullRep",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
ObjectBox({className: "null"},
|
||||
"null"
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
if (object && object.type && object.type == "null") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (object == null);
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Null = {
|
||||
rep: Null,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
46
devtools/client/shared/components/reps/number.js
Normal file
46
devtools/client/shared/components/reps/number.js
Normal file
@ -0,0 +1,46 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders a number
|
||||
*/
|
||||
const Number = React.createClass({
|
||||
displayName: "Number",
|
||||
|
||||
render: function() {
|
||||
let value = this.props.object;
|
||||
return (
|
||||
ObjectBox({className: "number"},
|
||||
this.stringify(value)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
stringify: function(object) {
|
||||
return (Object.is(object, -0) ? "-0" : String(object));
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return type == "boolean" || type == "number";
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Number = {
|
||||
rep: Number,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
35
devtools/client/shared/components/reps/object-box.js
Normal file
35
devtools/client/shared/components/reps/object-box.js
Normal file
@ -0,0 +1,35 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a box for given object.
|
||||
*/
|
||||
const ObjectBox = React.createClass({
|
||||
displayName: "ObjectBox",
|
||||
|
||||
render: function() {
|
||||
let className = this.props.className;
|
||||
let boxClassName = className ? " objectBox-" + className : "";
|
||||
|
||||
return (
|
||||
DOM.span({className: "objectBox" + boxClassName, role: "presentation"},
|
||||
this.props.children
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.ObjectBox = ObjectBox;
|
||||
});
|
36
devtools/client/shared/components/reps/object-link.js
Normal file
36
devtools/client/shared/components/reps/object-link.js
Normal file
@ -0,0 +1,36 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a link for given object.
|
||||
*/
|
||||
const ObjectLink = React.createClass({
|
||||
displayName: "ObjectLink",
|
||||
|
||||
render: function() {
|
||||
let className = this.props.className;
|
||||
let objectClassName = className ? " objectLink-" + className : "";
|
||||
let linkClassName = "objectLink" + objectClassName + " a11yFocus";
|
||||
|
||||
return (
|
||||
DOM.a({className: linkClassName, _repObject: this.props.object},
|
||||
this.props.children
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.ObjectLink = ObjectLink;
|
||||
});
|
190
devtools/client/shared/components/reps/object.js
Normal file
190
devtools/client/shared/components/reps/object.js
Normal file
@ -0,0 +1,190 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
const { Caption } = createFactories(require("./caption"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders an object. An object is represented by a list of its
|
||||
* properties enclosed in curly brackets.
|
||||
*/
|
||||
const Obj = React.createClass({
|
||||
displayName: "Obj",
|
||||
|
||||
render: function() {
|
||||
let object = this.props.object;
|
||||
let props = this.shortPropIterator(object);
|
||||
|
||||
return (
|
||||
ObjectBox({className: "object"},
|
||||
DOM.span({className: "objectTitle"}, this.getTitle(object)),
|
||||
DOM.span({className: "objectLeftBrace", role: "presentation"}, "{"),
|
||||
props,
|
||||
DOM.span({className: "objectRightBrace"}, "}")
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
getTitle: function() {
|
||||
return "";
|
||||
},
|
||||
|
||||
longPropIterator: function(object) {
|
||||
try {
|
||||
return this.propIterator(object, 100);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
shortPropIterator: function(object) {
|
||||
try {
|
||||
return this.propIterator(object, 3);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
propIterator: function(object, max) {
|
||||
function isInterestingProp(t, value) {
|
||||
return (t == "boolean" || t == "number" || (t == "string" && value) ||
|
||||
(t == "object" && value && value.toString));
|
||||
}
|
||||
|
||||
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
|
||||
if (Object.prototype.toString.call(object) === "[object Generator]") {
|
||||
object = Object.getPrototypeOf(object);
|
||||
}
|
||||
|
||||
// Object members with non-empty values are preferred since it gives the
|
||||
// user a better overview of the object.
|
||||
let props = [];
|
||||
this.getProps(props, object, max, isInterestingProp);
|
||||
|
||||
if (props.length <= max) {
|
||||
// There are not enough props yet (or at least, not enough props to
|
||||
// be able to know whether we should print "more..." or not).
|
||||
// Let's display also empty members and functions.
|
||||
this.getProps(props, object, max, function(t, value) {
|
||||
return !isInterestingProp(t, value);
|
||||
});
|
||||
}
|
||||
|
||||
if (props.length > max) {
|
||||
props.pop();
|
||||
props.push(Caption({
|
||||
key: "more",
|
||||
object: "more...",
|
||||
}));
|
||||
} else if (props.length > 0) {
|
||||
// Remove the last comma.
|
||||
props[props.length - 1] = React.cloneElement(
|
||||
props[props.length - 1], { delim: "" });
|
||||
}
|
||||
|
||||
return props;
|
||||
},
|
||||
|
||||
getProps: function(props, object, max, filter) {
|
||||
max = max || 3;
|
||||
if (!object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let mode = this.props.mode;
|
||||
|
||||
try {
|
||||
for (let name in object) {
|
||||
if (props.length > max) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let value;
|
||||
try {
|
||||
value = object[name];
|
||||
} catch (exc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let t = typeof value;
|
||||
if (filter(t, value)) {
|
||||
props.push(PropRep({
|
||||
key: name,
|
||||
mode: mode,
|
||||
name: name,
|
||||
object: value,
|
||||
equal: ": ",
|
||||
delim: ", ",
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders object property, name-value pair.
|
||||
*/
|
||||
let PropRep = React.createFactory(React.createClass({
|
||||
displayName: "PropRep",
|
||||
|
||||
render: function() {
|
||||
let { Rep } = createFactories(require("./rep"));
|
||||
let object = this.props.object;
|
||||
let mode = this.props.mode;
|
||||
|
||||
return (
|
||||
DOM.span({},
|
||||
DOM.span({
|
||||
"className": "nodeName"},
|
||||
this.props.name
|
||||
),
|
||||
DOM.span({
|
||||
"className": "objectEqual",
|
||||
role: "presentation"},
|
||||
this.props.equal
|
||||
),
|
||||
Rep({
|
||||
object: object,
|
||||
mode: mode
|
||||
}),
|
||||
DOM.span({
|
||||
"className": "objectComma",
|
||||
role: "presentation"},
|
||||
this.props.delim
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Obj = {
|
||||
rep: Obj,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
29
devtools/client/shared/components/reps/rep-utils.js
Normal file
29
devtools/client/shared/components/reps/rep-utils.js
Normal file
@ -0,0 +1,29 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
/**
|
||||
* Create React factories for given arguments.
|
||||
* Example:
|
||||
* const { Rep } = createFactories(require("./rep"));
|
||||
*/
|
||||
function createFactories(args) {
|
||||
let result = {};
|
||||
for (let p in args) {
|
||||
result[p] = React.createFactory(args[p]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.createFactories = createFactories;
|
||||
});
|
86
devtools/client/shared/components/reps/rep.js
Normal file
86
devtools/client/shared/components/reps/rep.js
Normal file
@ -0,0 +1,86 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Load all existing rep templates
|
||||
const { Undefined } = require("./undefined");
|
||||
const { Null } = require("./null");
|
||||
const { StringRep } = require("./string");
|
||||
const { Number } = require("./number");
|
||||
const { ArrayRep } = require("./array");
|
||||
const { Obj } = require("./object");
|
||||
|
||||
// List of all registered template.
|
||||
// XXX there should be a way for extensions to register a new
|
||||
// or modify an existing rep.
|
||||
let reps = [Undefined, Null, StringRep, Number, ArrayRep, Obj];
|
||||
let defaultRep;
|
||||
|
||||
/**
|
||||
* Generic rep that is using for rendering native JS types or an object.
|
||||
* The right template used for rendering is picked automatically according
|
||||
* to the current value type. The value must be passed is as 'object'
|
||||
* property.
|
||||
*/
|
||||
const Rep = React.createClass({
|
||||
displayName: "Rep",
|
||||
|
||||
render: function() {
|
||||
let rep = getRep(this.props.object);
|
||||
return rep(this.props);
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Return a rep object that is responsible for rendering given
|
||||
* object.
|
||||
*
|
||||
* @param object {Object} Object to be rendered in the UI. This
|
||||
* can be generic JS object as well as a grip (handle to a remote
|
||||
* debuggee object).
|
||||
*/
|
||||
function getRep(object) {
|
||||
let type = typeof object;
|
||||
if (type == "object" && object instanceof String) {
|
||||
type = "string";
|
||||
}
|
||||
|
||||
if (isGrip(object)) {
|
||||
type = object.class;
|
||||
}
|
||||
|
||||
for (let i = 0; i < reps.length; i++) {
|
||||
let rep = reps[i];
|
||||
try {
|
||||
// supportsObject could return weight (not only true/false
|
||||
// but a number), which would allow to priorities templates and
|
||||
// support better extensibility.
|
||||
if (rep.supportsObject(object, type)) {
|
||||
return React.createFactory(rep.rep);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("reps.getRep; EXCEPTION ", err, err);
|
||||
}
|
||||
}
|
||||
|
||||
return React.createFactory(defaultRep.rep);
|
||||
}
|
||||
|
||||
function isGrip(object) {
|
||||
return object && object.actor;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.Rep = Rep;
|
||||
});
|
101
devtools/client/shared/components/reps/string.js
Normal file
101
devtools/client/shared/components/reps/string.js
Normal file
@ -0,0 +1,101 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders a string. String value is enclosed within quotes.
|
||||
*/
|
||||
const StringRep = React.createClass({
|
||||
displayName: "StringRep",
|
||||
|
||||
render: function() {
|
||||
let text = this.props.object;
|
||||
let member = this.props.member;
|
||||
if (member && member.open) {
|
||||
return (
|
||||
ObjectBox({className: "string"},
|
||||
"\"" + text + "\""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
ObjectBox({className: "string"},
|
||||
"\"" + cropMultipleLines(text) + "\""
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
function escapeNewLines(value) {
|
||||
return value.replace(/\r/gm, "\\r").replace(/\n/gm, "\\n");
|
||||
}
|
||||
|
||||
function cropMultipleLines(text, limit) {
|
||||
return escapeNewLines(cropString(text, limit));
|
||||
}
|
||||
|
||||
function cropString(text, limit, alternativeText) {
|
||||
if (!alternativeText) {
|
||||
alternativeText = "...";
|
||||
}
|
||||
|
||||
// Make sure it's a string.
|
||||
text = text + "";
|
||||
|
||||
// Use default limit if necessary.
|
||||
if (!limit) {
|
||||
limit = 50;
|
||||
}
|
||||
|
||||
// Crop the string only if a limit is actually specified.
|
||||
if (limit <= 0) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Set the limit at least to the length of the alternative text
|
||||
// plus one character of the original text.
|
||||
if (limit <= alternativeText.length) {
|
||||
limit = alternativeText.length + 1;
|
||||
}
|
||||
|
||||
let halfLimit = (limit - alternativeText.length) / 2;
|
||||
|
||||
if (text.length > limit) {
|
||||
return text.substr(0, Math.ceil(halfLimit)) + alternativeText +
|
||||
text.substr(text.length - Math.floor(halfLimit));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function isCropped(value) {
|
||||
let cropLength = 50;
|
||||
return typeof value == "string" && value.length > cropLength;
|
||||
}
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return (type == "string");
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.StringRep = {
|
||||
rep: StringRep,
|
||||
supportsObject: supportsObject,
|
||||
isCropped: isCropped
|
||||
};
|
||||
});
|
45
devtools/client/shared/components/reps/undefined.js
Normal file
45
devtools/client/shared/components/reps/undefined.js
Normal file
@ -0,0 +1,45 @@
|
||||
/* -*- 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders undefined value
|
||||
*/
|
||||
const Undefined = React.createClass({
|
||||
displayName: "UndefinedRep",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
ObjectBox({className: "undefined"},
|
||||
"undefined"
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
if (object && object.type && object.type == "undefined") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (type == "undefined");
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Undefined = {
|
||||
rep: Undefined,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
@ -2,6 +2,7 @@
|
||||
support-files =
|
||||
head.js
|
||||
|
||||
[test_HSplitBox_01.html]
|
||||
[test_frame_01.html]
|
||||
[test_frame_02.html]
|
||||
[test_tree_01.html]
|
||||
|
@ -0,0 +1,122 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Basic tests for the HSplitBox component.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tree component test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript "src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/components-h-split-box.css" type="text/css"/>
|
||||
<style>
|
||||
html {
|
||||
--theme-splitter-color: black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
const FUDGE_FACTOR = .1;
|
||||
function aboutEq(a, b) {
|
||||
dumpn(`Checking ${a} ~= ${b}`);
|
||||
return Math.abs(a - b) < FUDGE_FACTOR;
|
||||
}
|
||||
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
const React = browserRequire("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
let HSplitBox = React.createFactory(browserRequire("devtools/client/shared/components/h-split-box"));
|
||||
ok(HSplitBox, "Should get HSplitBox");
|
||||
|
||||
const newSizes = [];
|
||||
const box = ReactDOM.render(HSplitBox({
|
||||
start: "hello!",
|
||||
end: "world!",
|
||||
startWidth: .5,
|
||||
onResize(newSize) {
|
||||
newSizes.push(newSize);
|
||||
},
|
||||
}), window.document.body);
|
||||
|
||||
// Test that we properly rendered our two panes.
|
||||
|
||||
let panes = document.querySelectorAll(".h-split-box-pane");
|
||||
is(panes.length, 2, "Should get two panes");
|
||||
is(panes[0].style.flexGrow, "0.5", "Each pane should have .5 width");
|
||||
is(panes[1].style.flexGrow, "0.5", "Each pane should have .5 width");
|
||||
is(panes[0].textContent.trim(), "hello!", "First pane should be hello");
|
||||
is(panes[1].textContent.trim(), "world!", "Second pane should be world");
|
||||
|
||||
// Now change the left width and assert that the changes are reflected.
|
||||
|
||||
yield setProps(box, { startWidth: .25 });
|
||||
panes = document.querySelectorAll(".h-split-box-pane");
|
||||
is(panes.length, 2, "Should still have two panes");
|
||||
is(panes[0].style.flexGrow, "0.25", "First pane's width should be .25");
|
||||
is(panes[1].style.flexGrow, "0.75", "Second pane's width should be .75");
|
||||
|
||||
// Mouse moves without having grabbed the splitter should have no effect.
|
||||
|
||||
let container = document.querySelector(".h-split-box");
|
||||
ok(container, "Should get our container .h-split-box");
|
||||
|
||||
const { left, top, width } = container.getBoundingClientRect();
|
||||
const middle = left + width / 2;
|
||||
const oneQuarter = left + width / 4;
|
||||
const threeQuarters = left + 3 * width / 4;
|
||||
|
||||
synthesizeMouse(container, middle, top, { type: "mousemove" }, window);
|
||||
is(newSizes.length, 0, "Mouse moves without dragging the splitter should have no effect");
|
||||
|
||||
// Send a mouse down on the splitter, and then move the mouse a couple
|
||||
// times. Now we should get resizes.
|
||||
|
||||
const splitter = document.querySelector(".h-split-box-splitter");
|
||||
ok(splitter, "Should get our splitter");
|
||||
|
||||
synthesizeMouseAtCenter(splitter, { button: 1, type: "mousedown" }, window);
|
||||
|
||||
function mouseMove(clientX) {
|
||||
const event = new MouseEvent("mousemove", { clientX });
|
||||
document.defaultView.top.dispatchEvent(event);
|
||||
}
|
||||
|
||||
mouseMove(middle);
|
||||
is(newSizes.length, 1, "Should get 1 resize");
|
||||
ok(aboutEq(newSizes[0], .5), "New size should be ~.5");
|
||||
|
||||
mouseMove(left);
|
||||
is(newSizes.length, 2, "Should get 2 resizes");
|
||||
ok(aboutEq(newSizes[1], 0), "New size should be ~0");
|
||||
|
||||
mouseMove(oneQuarter);
|
||||
is(newSizes.length, 3, "Sould get 3 resizes");
|
||||
ok(aboutEq(newSizes[2], .25), "New size should be ~.25");
|
||||
|
||||
mouseMove(threeQuarters);
|
||||
is(newSizes.length, 4, "Should get 4 resizes");
|
||||
ok(aboutEq(newSizes[3], .75), "New size should be ~.75");
|
||||
|
||||
synthesizeMouseAtCenter(splitter, { button: 1, type: "mouseup" }, window);
|
||||
|
||||
// Now that we have let go of the splitter, mouse moves should not result in resizes.
|
||||
|
||||
synthesizeMouse(container, middle, top, { type: "mousemove" }, window);
|
||||
is(newSizes.length, 4, "Should still have 4 resizes");
|
||||
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user