mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to UX on a CLOSED TREE
This commit is contained in:
commit
fd32f01bbc
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1368564124000">
|
||||
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1369429787000">
|
||||
<emItems>
|
||||
<emItem blockID="i350" id="sqlmoz@facebook.com">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
@ -416,10 +416,18 @@
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i358" id="lfind@nijadsoft.net">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i162" id="{EB7508CA-C7B2-46E0-8C04-3E94A035BD49}">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i354" id="{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i106" os="WINNT" id="{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}">
|
||||
<versionRange minVersion="0" maxVersion="15.0.5" severity="1">
|
||||
</versionRange>
|
||||
@ -485,6 +493,10 @@
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i356" id="{341f4dac-1966-47ff-aacf-0ce175f1498a}">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i70" id="psid-vhvxQHMZBOzUZA@jetpack">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
|
@ -1157,6 +1157,11 @@ pref("devtools.editor.component", "orion");
|
||||
// Enable the Font Inspector
|
||||
pref("devtools.fontinspector.enabled", true);
|
||||
|
||||
// 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
|
||||
// version for each user.
|
||||
pref("devtools.telemetry.tools.opened.version", "{}");
|
||||
|
||||
// Whether the character encoding menu is under the main Firefox button. This
|
||||
// preference is a string so that localizers can alter it.
|
||||
pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
|
||||
|
@ -1415,7 +1415,18 @@
|
||||
evt.initEvent("TabOpen", true, false);
|
||||
t.dispatchEvent(evt);
|
||||
|
||||
if (!uriIsAboutBlank) {
|
||||
// If we just created a new tab that loads the default
|
||||
// newtab url, swap in a preloaded page if possible.
|
||||
// Do nothing if we're a private window.
|
||||
let docShellsSwapped = false;
|
||||
if (aURI == BROWSER_NEW_TAB_URL &&
|
||||
!PrivateBrowsingUtils.isWindowPrivate(window)) {
|
||||
docShellsSwapped = gBrowserNewTabPreloader.newTab(t);
|
||||
}
|
||||
|
||||
// If we didn't swap docShells with a preloaded browser
|
||||
// then let's just continue loading the page normally.
|
||||
if (!docShellsSwapped && !uriIsAboutBlank) {
|
||||
// pretend the user typed this so it'll be available till
|
||||
// the document successfully loads
|
||||
if (aURI && gInitialPages.indexOf(aURI) == -1)
|
||||
@ -1439,14 +1450,6 @@
|
||||
// activeness in the tab switcher.
|
||||
b.docShellIsActive = false;
|
||||
|
||||
// If we just created a new tab that loads the default
|
||||
// newtab url, swap in a preloaded page if possible.
|
||||
// Do nothing if we're a private window.
|
||||
if (aURI == BROWSER_NEW_TAB_URL &&
|
||||
!PrivateBrowsingUtils.isWindowPrivate(window)) {
|
||||
gBrowserNewTabPreloader.newTab(t);
|
||||
}
|
||||
|
||||
// Check if we're opening a tab related to the current tab and
|
||||
// move it to after the current tab.
|
||||
// aReferrerURI is null or undefined if the tab is opened from
|
||||
|
@ -62,9 +62,10 @@ let TestRunner = {
|
||||
*/
|
||||
finish: function () {
|
||||
function cleanupAndFinish() {
|
||||
clearHistory();
|
||||
whenPagesUpdated(finish);
|
||||
NewTabUtils.restore();
|
||||
clearHistory(function () {
|
||||
whenPagesUpdated(finish);
|
||||
NewTabUtils.restore();
|
||||
});
|
||||
}
|
||||
|
||||
let callbacks = NewTabUtils.links._populateCallbacks;
|
||||
@ -128,16 +129,22 @@ function setLinks(aLinks) {
|
||||
});
|
||||
}
|
||||
|
||||
clearHistory();
|
||||
fillHistory(links, function () {
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
NewTabUtils.allPages.update();
|
||||
TestRunner.next();
|
||||
}, true);
|
||||
clearHistory(function () {
|
||||
fillHistory(links, function () {
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
NewTabUtils.allPages.update();
|
||||
TestRunner.next();
|
||||
}, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clearHistory() {
|
||||
function clearHistory(aCallback) {
|
||||
Services.obs.addObserver(function observe(aSubject, aTopic, aData) {
|
||||
Services.obs.removeObserver(observe, aTopic);
|
||||
executeSoon(aCallback);
|
||||
}, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
|
||||
|
||||
PlacesUtils.history.removeAllPages();
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,9 @@ const STATE_QUITTING = -1;
|
||||
const STATE_STOPPED_STR = "stopped";
|
||||
const STATE_RUNNING_STR = "running";
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
const PRIVACY_NONE = 0;
|
||||
const PRIVACY_ENCRYPTED = 1;
|
||||
const PRIVACY_FULL = 2;
|
||||
@ -232,29 +235,6 @@ this.SessionStore = {
|
||||
|
||||
checkPrivacyLevel: function ss_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
|
||||
return SessionStoreInternal.checkPrivacyLevel(aIsHTTPS, aUseDefaultPref);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether a given browser is waiting to be restored. That means its
|
||||
* history and state is ready but we wait until it's higher up in the priority
|
||||
* queue or until it's made visible (if restore_on_demand=true).
|
||||
*
|
||||
* @param aBrowser Browser reference
|
||||
* @returns bool
|
||||
*/
|
||||
isTabStateNeedsRestore: function ss_isTabStateNeedsRestore(aBrowser) {
|
||||
return TabRestoreStates.isNeedsRestore(aBrowser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether a given browser is currently restoring, i.e. we wait for
|
||||
* the actual page to load and will restore form data when it's finished.
|
||||
*
|
||||
* @param aBrowser Browser reference
|
||||
* @returns bool
|
||||
*/
|
||||
isTabStateRestoring: function ss_isTabStateRestoring(aBrowser) {
|
||||
return TabRestoreStates.isRestoring(aBrowser);
|
||||
}
|
||||
};
|
||||
|
||||
@ -310,6 +290,12 @@ let SessionStoreInternal = {
|
||||
// number of tabs currently restoring
|
||||
_tabsRestoringCount: 0,
|
||||
|
||||
// overrides MAX_CONCURRENT_TAB_RESTORES and restore_hidden_tabs when true
|
||||
_restoreOnDemand: false,
|
||||
|
||||
// whether to restore app tabs on demand or not, pref controlled.
|
||||
_restorePinnedTabsOnDemand: null,
|
||||
|
||||
// The state from the previous session (after restoring pinned tabs). This
|
||||
// state is persisted and passed through to the next session during an app
|
||||
// restart to make the third party add-on warning not trash the deferred
|
||||
@ -385,6 +371,14 @@ let SessionStoreInternal = {
|
||||
this._sessionhistory_max_entries =
|
||||
this._prefBranch.getIntPref("sessionhistory.max_entries");
|
||||
|
||||
this._restoreOnDemand =
|
||||
this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
|
||||
this._prefBranch.addObserver("sessionstore.restore_on_demand", this, true);
|
||||
|
||||
this._restorePinnedTabsOnDemand =
|
||||
this._prefBranch.getBoolPref("sessionstore.restore_pinned_tabs_on_demand");
|
||||
this._prefBranch.addObserver("sessionstore.restore_pinned_tabs_on_demand", this, true);
|
||||
|
||||
gSessionStartup.onceInitialized.then(
|
||||
this.initSession.bind(this)
|
||||
);
|
||||
@ -1059,11 +1053,12 @@ let SessionStoreInternal = {
|
||||
this._lastSessionState = null;
|
||||
let openWindows = {};
|
||||
this._forEachBrowserWindow(function(aWindow) {
|
||||
Array.forEach(aWindow.gBrowser.tabs, aTab => {
|
||||
RestoringTabsData.remove(aTab.linkedBrowser);
|
||||
FormDataCache.remove(aTab.linkedBrowser);
|
||||
Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
|
||||
delete aTab.linkedBrowser.__SS_data;
|
||||
delete aTab.linkedBrowser.__SS_tabStillLoading;
|
||||
delete aTab.linkedBrowser.__SS_formDataSaved;
|
||||
delete aTab.linkedBrowser.__SS_hostSchemeData;
|
||||
if (TabRestoreStates.has(aTab.linkedBrowser))
|
||||
if (aTab.linkedBrowser.__SS_restoreState)
|
||||
this._resetTabRestoringState(aTab);
|
||||
});
|
||||
openWindows[aWindow.__SSi] = true;
|
||||
@ -1193,6 +1188,14 @@ let SessionStoreInternal = {
|
||||
_SessionFile.wipe();
|
||||
this.saveState(true);
|
||||
break;
|
||||
case "sessionstore.restore_on_demand":
|
||||
this._restoreOnDemand =
|
||||
this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
|
||||
break;
|
||||
case "sessionstore.restore_pinned_tabs_on_demand":
|
||||
this._restorePinnedTabsOnDemand =
|
||||
this._prefBranch.getBoolPref("sessionstore.restore_pinned_tabs_on_demand");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -1243,17 +1246,18 @@ let SessionStoreInternal = {
|
||||
let mm = browser.messageManager;
|
||||
MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
|
||||
|
||||
RestoringTabsData.remove(browser);
|
||||
FormDataCache.remove(browser);
|
||||
delete browser.__SS_data;
|
||||
delete browser.__SS_tabStillLoading;
|
||||
delete browser.__SS_formDataSaved;
|
||||
delete browser.__SS_hostSchemeData;
|
||||
|
||||
// If this tab was in the middle of restoring or still needs to be restored,
|
||||
// we need to reset that state. If the tab was restoring, we will attempt to
|
||||
// restore the next tab.
|
||||
if (TabRestoreStates.has(browser)) {
|
||||
let wasRestoring = TabRestoreStates.isRestoring(browser);
|
||||
let previousState = browser.__SS_restoreState;
|
||||
if (previousState) {
|
||||
this._resetTabRestoringState(aTab);
|
||||
if (wasRestoring)
|
||||
if (previousState == TAB_STATE_RESTORING)
|
||||
this.restoreNextTab();
|
||||
}
|
||||
|
||||
@ -1283,7 +1287,7 @@ let SessionStoreInternal = {
|
||||
|
||||
// make sure that the tab related data is up-to-date
|
||||
var tabState = this._collectTabData(aTab);
|
||||
this._updateTextAndScrollDataForTab(aTab.linkedBrowser, tabState);
|
||||
this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState);
|
||||
|
||||
// store closed-tab data for undo
|
||||
if (this._shouldSaveTabState(tabState)) {
|
||||
@ -1315,12 +1319,14 @@ let SessionStoreInternal = {
|
||||
// following "load" is too late for deleting the data caches)
|
||||
// It's possible to get a load event after calling stop on a browser (when
|
||||
// overwriting tabs). We want to return early if the tab hasn't been restored yet.
|
||||
if (TabRestoreStates.isNeedsRestore(aBrowser)) {
|
||||
if (aBrowser.__SS_restoreState &&
|
||||
aBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
|
||||
return;
|
||||
}
|
||||
|
||||
RestoringTabsData.remove(aBrowser);
|
||||
FormDataCache.remove(aBrowser);
|
||||
delete aBrowser.__SS_data;
|
||||
delete aBrowser.__SS_tabStillLoading;
|
||||
delete aBrowser.__SS_formDataSaved;
|
||||
this.saveStateDelayed(aWindow);
|
||||
|
||||
// attempt to update the current URL we send in a crash report
|
||||
@ -1335,7 +1341,9 @@ let SessionStoreInternal = {
|
||||
* Browser reference
|
||||
*/
|
||||
onTabInput: function ssi_onTabInput(aWindow, aBrowser) {
|
||||
FormDataCache.remove(aBrowser);
|
||||
// deleting __SS_formDataSaved will cause us to recollect form data
|
||||
delete aBrowser.__SS_formDataSaved;
|
||||
|
||||
this.saveStateDelayed(aWindow, 3000);
|
||||
},
|
||||
|
||||
@ -1349,8 +1357,11 @@ let SessionStoreInternal = {
|
||||
this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
|
||||
|
||||
let tab = aWindow.gBrowser.selectedTab;
|
||||
// Explicitly call restoreTab() to to restore the tab if we need to.
|
||||
if (TabRestoreStates.isNeedsRestore(tab.linkedBrowser))
|
||||
// If __SS_restoreState is still on the browser and it is
|
||||
// TAB_STATE_NEEDS_RESTORE, then then we haven't restored
|
||||
// this tab yet. Explicitly call restoreTab to kick off the restore.
|
||||
if (tab.linkedBrowser.__SS_restoreState &&
|
||||
tab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
|
||||
this.restoreTab(tab);
|
||||
|
||||
// attempt to update the current URL we send in a crash report
|
||||
@ -1360,7 +1371,8 @@ let SessionStoreInternal = {
|
||||
|
||||
onTabShow: function ssi_onTabShow(aWindow, aTab) {
|
||||
// If the tab hasn't been restored yet, move it into the right bucket
|
||||
if (TabRestoreStates.isNeedsRestore(aTab.linkedBrowser)) {
|
||||
if (aTab.linkedBrowser.__SS_restoreState &&
|
||||
aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
|
||||
TabRestoreQueue.hiddenToVisible(aTab);
|
||||
|
||||
// let's kick off tab restoration again to ensure this tab gets restored
|
||||
@ -1375,7 +1387,8 @@ let SessionStoreInternal = {
|
||||
|
||||
onTabHide: function ssi_onTabHide(aWindow, aTab) {
|
||||
// If the tab hasn't been restored yet, move it into the right bucket
|
||||
if (TabRestoreStates.isNeedsRestore(aTab.linkedBrowser)) {
|
||||
if (aTab.linkedBrowser.__SS_restoreState &&
|
||||
aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
|
||||
TabRestoreQueue.visibleToHidden(aTab);
|
||||
}
|
||||
|
||||
@ -1455,7 +1468,10 @@ let SessionStoreInternal = {
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
|
||||
var tabState = this._collectTabData(aTab);
|
||||
this._updateTextAndScrollDataForTab(aTab.linkedBrowser, tabState);
|
||||
|
||||
var window = aTab.ownerDocument.defaultView;
|
||||
this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState);
|
||||
|
||||
return this._toJSONString(tabState);
|
||||
},
|
||||
|
||||
@ -1475,7 +1491,8 @@ let SessionStoreInternal = {
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
|
||||
var tabState = this._collectTabData(aTab, true);
|
||||
this._updateTextAndScrollDataForTab(aTab.linkedBrowser, tabState, true);
|
||||
var sourceWindow = aTab.ownerDocument.defaultView;
|
||||
this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
|
||||
tabState.index += aDelta;
|
||||
tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
|
||||
tabState.pinned = false;
|
||||
@ -1629,13 +1646,10 @@ let SessionStoreInternal = {
|
||||
let data = {};
|
||||
if (aTab.__SS_extdata) {
|
||||
data = aTab.__SS_extdata;
|
||||
} else {
|
||||
let tabData = RestoringTabsData.get(aTab.linkedBrowser);
|
||||
if (tabData && "extData" in tabData) {
|
||||
// If the tab hasn't been fully restored yet,
|
||||
// get the data from the to-be-restored data.
|
||||
data = tabData.extData;
|
||||
}
|
||||
}
|
||||
else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
|
||||
// If the tab hasn't been fully restored, get the data from the to-be-restored data
|
||||
data = aTab.linkedBrowser.__SS_data.extData;
|
||||
}
|
||||
return data[aKey] || "";
|
||||
},
|
||||
@ -1646,14 +1660,13 @@ let SessionStoreInternal = {
|
||||
let saveTo;
|
||||
if (aTab.__SS_extdata) {
|
||||
saveTo = aTab.__SS_extdata;
|
||||
} else {
|
||||
let tabData = RestoringTabsData.get(aTab.linkedBrowser);
|
||||
if (tabData && "extData" in tabData) {
|
||||
saveTo = tabData.extData;
|
||||
} else {
|
||||
aTab.__SS_extdata = {};
|
||||
saveTo = aTab.__SS_extdata;
|
||||
}
|
||||
}
|
||||
else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
|
||||
saveTo = aTab.linkedBrowser.__SS_data.extData;
|
||||
}
|
||||
else {
|
||||
aTab.__SS_extdata = {};
|
||||
saveTo = aTab.__SS_extdata;
|
||||
}
|
||||
saveTo[aKey] = aStringValue;
|
||||
this.saveStateDelayed(aTab.ownerDocument.defaultView);
|
||||
@ -1661,16 +1674,14 @@ let SessionStoreInternal = {
|
||||
|
||||
deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
|
||||
// We want to make sure that if data is accessed early, we attempt to delete
|
||||
// that data from to-be-restored data as well. Otherwise we'll throw in
|
||||
// cases where data can be set or read.
|
||||
// that data from __SS_data as well. Otherwise we'll throw in cases where
|
||||
// data can be set or read.
|
||||
let deleteFrom;
|
||||
if (aTab.__SS_extdata) {
|
||||
deleteFrom = aTab.__SS_extdata;
|
||||
} else {
|
||||
let tabData = RestoringTabsData.get(aTab.linkedBrowser);
|
||||
if (tabData && "extData" in tabData) {
|
||||
deleteFrom = tabData.extData;
|
||||
}
|
||||
}
|
||||
else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
|
||||
deleteFrom = aTab.linkedBrowser.__SS_data.extData;
|
||||
}
|
||||
|
||||
if (deleteFrom && deleteFrom[aKey])
|
||||
@ -1882,9 +1893,9 @@ let SessionStoreInternal = {
|
||||
if (!browser || !browser.currentURI)
|
||||
// can happen when calling this function right after .addTab()
|
||||
return tabData;
|
||||
else if (RestoringTabsData.has(browser)) {
|
||||
else if (browser.__SS_data && browser.__SS_tabStillLoading) {
|
||||
// use the data to be restored when the tab hasn't been completely loaded
|
||||
tabData = RestoringTabsData.get(browser);
|
||||
tabData = browser.__SS_data;
|
||||
if (aTab.pinned)
|
||||
tabData.pinned = true;
|
||||
else
|
||||
@ -1907,7 +1918,16 @@ let SessionStoreInternal = {
|
||||
}
|
||||
catch (ex) { } // this could happen if we catch a tab during (de)initialization
|
||||
|
||||
if (history && history.count > 0) {
|
||||
// XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse
|
||||
// data even when we shouldn't (e.g. Back, different anchor)
|
||||
if (history && browser.__SS_data &&
|
||||
browser.__SS_data.entries[history.index] &&
|
||||
browser.__SS_data.entries[history.index].url == browser.currentURI.spec &&
|
||||
history.index < this._sessionhistory_max_entries - 1 && !aFullData) {
|
||||
tabData = browser.__SS_data;
|
||||
tabData.index = history.index + 1;
|
||||
}
|
||||
else if (history && history.count > 0) {
|
||||
browser.__SS_hostSchemeData = [];
|
||||
try {
|
||||
for (var j = 0; j < history.count; j++) {
|
||||
@ -1936,6 +1956,10 @@ let SessionStoreInternal = {
|
||||
}
|
||||
}
|
||||
tabData.index = history.index + 1;
|
||||
|
||||
// make sure not to cache privacy sensitive data which shouldn't get out
|
||||
if (!aFullData)
|
||||
browser.__SS_data = tabData;
|
||||
}
|
||||
else if (browser.currentURI.spec != "about:blank" ||
|
||||
browser.contentDocument.body.hasChildNodes()) {
|
||||
@ -2142,7 +2166,7 @@ let SessionStoreInternal = {
|
||||
var browsers = aWindow.gBrowser.browsers;
|
||||
this._windows[aWindow.__SSi].tabs.forEach(function (tabData, i) {
|
||||
try {
|
||||
this._updateTextAndScrollDataForTab(browsers[i], tabData);
|
||||
this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
|
||||
}
|
||||
catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
|
||||
}, this);
|
||||
@ -2151,6 +2175,8 @@ let SessionStoreInternal = {
|
||||
/**
|
||||
* go through all frames and store the current scroll positions
|
||||
* and innerHTML content of WYSIWYG editors
|
||||
* @param aWindow
|
||||
* Window reference
|
||||
* @param aBrowser
|
||||
* single browser reference
|
||||
* @param aTabData
|
||||
@ -2159,9 +2185,9 @@ let SessionStoreInternal = {
|
||||
* always return privacy sensitive data (use with care)
|
||||
*/
|
||||
_updateTextAndScrollDataForTab:
|
||||
function ssi_updateTextAndScrollDataForTab(aBrowser, aTabData, aFullData) {
|
||||
function ssi_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
|
||||
// we shouldn't update data for incompletely initialized tabs
|
||||
if (RestoringTabsData.has(aBrowser))
|
||||
if (aBrowser.__SS_data && aBrowser.__SS_tabStillLoading)
|
||||
return;
|
||||
|
||||
var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
|
||||
@ -2176,10 +2202,11 @@ let SessionStoreInternal = {
|
||||
else if (aTabData.pageStyle)
|
||||
delete aTabData.pageStyle;
|
||||
|
||||
this._updateTextAndScrollDataForFrame(aBrowser, aBrowser.contentWindow,
|
||||
this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
|
||||
aTabData.entries[tabIndex],
|
||||
aFullData, !!aTabData.pinned);
|
||||
|
||||
!aBrowser.__SS_formDataSaved, aFullData,
|
||||
!!aTabData.pinned);
|
||||
aBrowser.__SS_formDataSaved = true;
|
||||
if (aBrowser.currentURI.spec == "about:config")
|
||||
aTabData.entries[tabIndex].formdata = {
|
||||
id: {
|
||||
@ -2192,8 +2219,8 @@ let SessionStoreInternal = {
|
||||
/**
|
||||
* go through all subframes and store all form data, the current
|
||||
* scroll positions and innerHTML content of WYSIWYG editors
|
||||
* @param aBrowser
|
||||
* single browser reference
|
||||
* @param aWindow
|
||||
* Window reference
|
||||
* @param aContent
|
||||
* frame reference
|
||||
* @param aData
|
||||
@ -2206,19 +2233,19 @@ let SessionStoreInternal = {
|
||||
* the tab is pinned and should be treated differently for privacy
|
||||
*/
|
||||
_updateTextAndScrollDataForFrame:
|
||||
function ssi_updateTextAndScrollDataForFrame(aBrowser, aContent, aData,
|
||||
aFullData, aIsPinned) {
|
||||
function ssi_updateTextAndScrollDataForFrame(aWindow, aContent, aData,
|
||||
aUpdateFormData, aFullData, aIsPinned) {
|
||||
for (var i = 0; i < aContent.frames.length; i++) {
|
||||
if (aData.children && aData.children[i])
|
||||
this._updateTextAndScrollDataForFrame(aBrowser, aContent.frames[i],
|
||||
aData.children[i], aFullData,
|
||||
aIsPinned);
|
||||
this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
|
||||
aData.children[i], aUpdateFormData,
|
||||
aFullData, aIsPinned);
|
||||
}
|
||||
var isHTTPS = this._getURIFromString((aContent.parent || aContent).
|
||||
document.location.href).schemeIs("https");
|
||||
let isAboutSR = aContent.top.document.location.href == "about:sessionrestore";
|
||||
if (aFullData || this.checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
|
||||
if (aFullData || !FormDataCache.has(aBrowser, aContent)) {
|
||||
if (aFullData || aUpdateFormData) {
|
||||
let formData = DocumentUtils.getFormData(aContent.document);
|
||||
|
||||
// We want to avoid saving data for about:sessionrestore as a string.
|
||||
@ -2230,18 +2257,9 @@ let SessionStoreInternal = {
|
||||
|
||||
if (Object.keys(formData.id).length ||
|
||||
Object.keys(formData.xpath).length) {
|
||||
aData.formdata = formData
|
||||
} else {
|
||||
formData = null;
|
||||
}
|
||||
|
||||
// Store form data in cache.
|
||||
FormDataCache.set(aBrowser, aContent, formData);
|
||||
} else {
|
||||
// Copy from form data cache.
|
||||
let cached = FormDataCache.get(aBrowser, aContent);
|
||||
if (cached) {
|
||||
aData.formdata = cached;
|
||||
aData.formdata = formData;
|
||||
} else if (aData.formdata) {
|
||||
delete aData.formdata;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2748,7 +2766,7 @@ let SessionStoreInternal = {
|
||||
// state (in restoreHistoryPrecursor).
|
||||
if (aOverwriteTabs) {
|
||||
for (let i = 0; i < tabbrowser.tabs.length; i++) {
|
||||
if (TabRestoreStates.has(tabbrowser.browsers[i]))
|
||||
if (tabbrowser.browsers[i].__SS_restoreState)
|
||||
this._resetTabRestoringState(tabbrowser.tabs[i]);
|
||||
}
|
||||
}
|
||||
@ -2975,10 +2993,12 @@ let SessionStoreInternal = {
|
||||
Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
|
||||
}
|
||||
|
||||
browser.__SS_tabStillLoading = true;
|
||||
|
||||
// keep the data around to prevent dataloss in case
|
||||
// a tab gets closed before it's been properly restored
|
||||
RestoringTabsData.set(browser, tabData);
|
||||
TabRestoreStates.setNeedsRestore(browser);
|
||||
browser.__SS_data = tabData;
|
||||
browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
|
||||
browser.setAttribute("pending", "true");
|
||||
tab.setAttribute("pending", "true");
|
||||
|
||||
@ -3151,7 +3171,7 @@ let SessionStoreInternal = {
|
||||
restoreTab: function ssi_restoreTab(aTab) {
|
||||
let window = aTab.ownerDocument.defaultView;
|
||||
let browser = aTab.linkedBrowser;
|
||||
let tabData = RestoringTabsData.get(browser);
|
||||
let tabData = browser.__SS_data;
|
||||
|
||||
// There are cases within where we haven't actually started a load. In that
|
||||
// that case we'll reset state changes we made and return false to the caller
|
||||
@ -3168,7 +3188,7 @@ let SessionStoreInternal = {
|
||||
this._tabsRestoringCount++;
|
||||
|
||||
// Set this tab's state to restoring
|
||||
TabRestoreStates.setIsRestoring(browser);
|
||||
browser.__SS_restoreState = TAB_STATE_RESTORING;
|
||||
browser.removeAttribute("pending");
|
||||
aTab.removeAttribute("pending");
|
||||
|
||||
@ -3246,8 +3266,10 @@ let SessionStoreInternal = {
|
||||
if (this._loadState == STATE_QUITTING)
|
||||
return;
|
||||
|
||||
// Don't exceed the maximum number of concurrent tab restores.
|
||||
if (this._tabsRestoringCount >= MAX_CONCURRENT_TAB_RESTORES)
|
||||
// If it's not possible to restore anything, then just bail out.
|
||||
if ((this._restoreOnDemand &&
|
||||
(this._restorePinnedTabsOnDemand || !TabRestoreQueue.hasPriorityTabs)) ||
|
||||
this._tabsRestoringCount >= MAX_CONCURRENT_TAB_RESTORES)
|
||||
return;
|
||||
|
||||
let tab = TabRestoreQueue.shift();
|
||||
@ -4089,13 +4111,15 @@ let SessionStoreInternal = {
|
||||
/**
|
||||
* Determine if we can restore history into this tab.
|
||||
* This will be false when a tab has been removed (usually between
|
||||
* restoreHistoryPrecursor && restoreHistory).
|
||||
* restoreHistoryPrecursor && restoreHistory) or if the tab is still marked
|
||||
* as loading.
|
||||
*
|
||||
* @param aTab
|
||||
* @returns boolean
|
||||
*/
|
||||
_canRestoreTabHistory: function ssi_canRestoreTabHistory(aTab) {
|
||||
return aTab.parentNode && aTab.linkedBrowser;
|
||||
return aTab.parentNode && aTab.linkedBrowser &&
|
||||
aTab.linkedBrowser.__SS_tabStillLoading;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -4390,11 +4414,10 @@ let SessionStoreInternal = {
|
||||
let browser = aTab.linkedBrowser;
|
||||
|
||||
// Keep the tab's previous state for later in this method
|
||||
let wasRestoring = TabRestoreStates.isRestoring(browser);
|
||||
let wasNeedsRestore = TabRestoreStates.isNeedsRestore(browser);
|
||||
let previousState = browser.__SS_restoreState;
|
||||
|
||||
// The browser is no longer in any sort of restoring state.
|
||||
TabRestoreStates.remove(browser);
|
||||
delete browser.__SS_restoreState;
|
||||
|
||||
aTab.removeAttribute("pending");
|
||||
browser.removeAttribute("pending");
|
||||
@ -4406,11 +4429,11 @@ let SessionStoreInternal = {
|
||||
// Remove the progress listener if we should.
|
||||
this._removeTabsProgressListener(window);
|
||||
|
||||
if (wasRestoring) {
|
||||
if (previousState == TAB_STATE_RESTORING) {
|
||||
if (this._tabsRestoringCount)
|
||||
this._tabsRestoringCount--;
|
||||
}
|
||||
else if (wasNeedsRestore) {
|
||||
else if (previousState == TAB_STATE_NEEDS_RESTORE) {
|
||||
// Make sure the session history listener is removed. This is normally
|
||||
// done in restoreTab, but this tab is being removed before that gets called.
|
||||
this._removeSHistoryListener(aTab);
|
||||
@ -4473,47 +4496,20 @@ let TabRestoreQueue = {
|
||||
// The separate buckets used to store tabs.
|
||||
tabs: {priority: [], visible: [], hidden: []},
|
||||
|
||||
// Preferences used by the TabRestoreQueue to determine which tabs
|
||||
// are restored automatically and which tabs will be on-demand.
|
||||
prefs: {
|
||||
// Lazy getter that returns whether tabs are restored on demand.
|
||||
get restoreOnDemand() {
|
||||
let updateValue = () => {
|
||||
let value = Services.prefs.getBoolPref(PREF);
|
||||
let definition = {value: value, configurable: true};
|
||||
Object.defineProperty(this, "restoreOnDemand", definition);
|
||||
}
|
||||
// Returns whether we have any high priority tabs in the queue.
|
||||
get hasPriorityTabs() !!this.tabs.priority.length,
|
||||
|
||||
const PREF = "browser.sessionstore.restore_on_demand";
|
||||
Services.prefs.addObserver(PREF, updateValue, false);
|
||||
updateValue();
|
||||
},
|
||||
|
||||
// Lazy getter that returns whether pinned tabs are restored on demand.
|
||||
get restorePinnedTabsOnDemand() {
|
||||
let updateValue = () => {
|
||||
let value = Services.prefs.getBoolPref(PREF);
|
||||
let definition = {value: value, configurable: true};
|
||||
Object.defineProperty(this, "restorePinnedTabsOnDemand", definition);
|
||||
}
|
||||
|
||||
const PREF = "browser.sessionstore.restore_pinned_tabs_on_demand";
|
||||
Services.prefs.addObserver(PREF, updateValue, false);
|
||||
updateValue();
|
||||
},
|
||||
|
||||
// Lazy getter that returns whether we should restore hidden tabs.
|
||||
get restoreHiddenTabs() {
|
||||
let updateValue = () => {
|
||||
let value = Services.prefs.getBoolPref(PREF);
|
||||
let definition = {value: value, configurable: true};
|
||||
Object.defineProperty(this, "restoreHiddenTabs", definition);
|
||||
}
|
||||
|
||||
const PREF = "browser.sessionstore.restore_hidden_tabs";
|
||||
Services.prefs.addObserver(PREF, updateValue, false);
|
||||
updateValue();
|
||||
// Lazy getter that returns whether we should restore hidden tabs.
|
||||
get restoreHiddenTabs() {
|
||||
let updateValue = () => {
|
||||
let value = Services.prefs.getBoolPref(PREF);
|
||||
let definition = {value: value, configurable: true};
|
||||
Object.defineProperty(this, "restoreHiddenTabs", definition);
|
||||
}
|
||||
|
||||
const PREF = "browser.sessionstore.restore_hidden_tabs";
|
||||
Services.prefs.addObserver(PREF, updateValue, false);
|
||||
updateValue();
|
||||
},
|
||||
|
||||
// Resets the queue and removes all tabs.
|
||||
@ -4558,16 +4554,12 @@ let TabRestoreQueue = {
|
||||
let set;
|
||||
let {priority, hidden, visible} = this.tabs;
|
||||
|
||||
let {restoreOnDemand, restorePinnedTabsOnDemand} = this.prefs;
|
||||
let restorePinned = !(restoreOnDemand && restorePinnedTabsOnDemand);
|
||||
if (restorePinned && priority.length) {
|
||||
if (priority.length) {
|
||||
set = priority;
|
||||
} else if (!restoreOnDemand) {
|
||||
if (visible.length) {
|
||||
set = visible;
|
||||
} else if (this.prefs.restoreHiddenTabs && hidden.length) {
|
||||
set = hidden;
|
||||
}
|
||||
} else if (visible.length) {
|
||||
set = visible;
|
||||
} else if (this.restoreHiddenTabs && hidden.length) {
|
||||
set = hidden;
|
||||
}
|
||||
|
||||
return set && set.shift();
|
||||
@ -4600,31 +4592,6 @@ let TabRestoreQueue = {
|
||||
}
|
||||
};
|
||||
|
||||
// A map storing tabData belonging to xul:browsers of tabs. This will hold data
|
||||
// while a tab is restoring (i.e. loading). Because we can't query or use the
|
||||
// incomplete state of a loading tab we'll use data stored in the map if browser
|
||||
// state is collected while a tab is still restoring or if it's closed before
|
||||
// having restored fully.
|
||||
let RestoringTabsData = {
|
||||
_data: new WeakMap(),
|
||||
|
||||
has: function (browser) {
|
||||
return this._data.has(browser);
|
||||
},
|
||||
|
||||
get: function (browser) {
|
||||
return this._data.get(browser);
|
||||
},
|
||||
|
||||
set: function (browser, data) {
|
||||
this._data.set(browser, data);
|
||||
},
|
||||
|
||||
remove: function (browser) {
|
||||
this._data.delete(browser);
|
||||
}
|
||||
};
|
||||
|
||||
// A map storing a closed window's state data until it goes aways (is GC'ed).
|
||||
// This ensures that API clients can still read (but not write) states of
|
||||
// windows they still hold a reference to but we don't.
|
||||
@ -4694,74 +4661,6 @@ let TabAttributes = {
|
||||
}
|
||||
};
|
||||
|
||||
// A map keeping track of all tab restore states. A tab might be 'needs-restore'
|
||||
// if it waits until the restoration process is kicked off. This might start
|
||||
// when the tab reaches a higher position in the priority queue or when it's
|
||||
// made visible (when restore_on_demand=true). If a tab is 'restoring' we wait
|
||||
// for its actual page to load and will then restore form data etc. If has()
|
||||
// returns false the tab has not been restored from previous data or it has
|
||||
// already finished restoring and is thus now seen as a valid and complete tab.
|
||||
let TabRestoreStates = {
|
||||
_states: new WeakMap(),
|
||||
|
||||
has: function (browser) {
|
||||
return this._states.has(browser);
|
||||
},
|
||||
|
||||
isNeedsRestore: function ss_isNeedsRestore(browser) {
|
||||
return this._states.get(browser) === "needs-restore";
|
||||
},
|
||||
|
||||
setNeedsRestore: function (browser) {
|
||||
this._states.set(browser, "needs-restore");
|
||||
},
|
||||
|
||||
isRestoring: function ss_isRestoring(browser) {
|
||||
return this._states.get(browser) === "restoring";
|
||||
},
|
||||
|
||||
setIsRestoring: function (browser) {
|
||||
this._states.set(browser, "restoring");
|
||||
},
|
||||
|
||||
remove: function (browser) {
|
||||
this._states.delete(browser);
|
||||
}
|
||||
};
|
||||
|
||||
// A map storing cached form data belonging to browsers. Each browser itself
|
||||
// has a WeakMap assigned that holds the form data of its main and subframes.
|
||||
let FormDataCache = {
|
||||
// Form data is cached using a nested data structure
|
||||
// of type WeakMap<browser, WeakMap<frame, data>>.
|
||||
_cache: new WeakMap(),
|
||||
|
||||
has: function (browser, frame) {
|
||||
return this._cache.has(browser) && this._cache.get(browser).has(frame);
|
||||
},
|
||||
|
||||
get: function (browser, frame) {
|
||||
return this._cache.get(browser).get(frame);
|
||||
},
|
||||
|
||||
set: function (browser, frame, data = {}) {
|
||||
if (!this._cache.has(browser)) {
|
||||
this._cache.set(browser, new WeakMap());
|
||||
}
|
||||
|
||||
// The object is frozen so that clients cannot mutate cached data.
|
||||
if (data && typeof data === "object") {
|
||||
Object.freeze(data);
|
||||
}
|
||||
|
||||
this._cache.get(browser).set(frame, data);
|
||||
},
|
||||
|
||||
remove: function (browser) {
|
||||
this._cache.delete(browser);
|
||||
}
|
||||
};
|
||||
|
||||
// This is used to help meter the number of restoring tabs. This is the control
|
||||
// point for telling the next tab to restore. It gets attached to each gBrowser
|
||||
// via gBrowser.addTabsProgressListener
|
||||
@ -4769,7 +4668,8 @@ let gRestoreTabsProgressListener = {
|
||||
onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
// Ignore state changes on browsers that we've already restored and state
|
||||
// changes that aren't applicable.
|
||||
if (TabRestoreStates.isRestoring(aBrowser) &&
|
||||
if (aBrowser.__SS_restoreState &&
|
||||
aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
|
||||
|
@ -18,6 +18,9 @@ function test() {
|
||||
// Undo pinning
|
||||
gBrowser.unpinTab(tab1);
|
||||
|
||||
is(tab1.linkedBrowser.__SS_tabStillLoading, true,
|
||||
"_tabStillLoading should be true.");
|
||||
|
||||
// Close and restore tab
|
||||
gBrowser.removeTab(tab1);
|
||||
let savedState = JSON.parse(ss.getClosedTabData(window))[0].state;
|
||||
|
@ -73,7 +73,7 @@ let TabsProgressListener = {
|
||||
},
|
||||
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (this.callback && SessionStore.isTabStateRestoring(aBrowser) &&
|
||||
if (this.callback && aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
@ -85,9 +85,9 @@ let TabsProgressListener = {
|
||||
|
||||
for (let i = 0; i < this.window.gBrowser.tabs.length; i++) {
|
||||
let browser = this.window.gBrowser.tabs[i].linkedBrowser;
|
||||
if (SessionStore.isTabStateRestoring(browser))
|
||||
if (browser.__SS_restoreState == TAB_STATE_RESTORING)
|
||||
isRestoring++;
|
||||
else if (SessionStore.isTabStateNeedsRestore(browser))
|
||||
else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
|
||||
needsRestore++;
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,17 @@ function test() {
|
||||
// any given time. This guarantees that a finishing load won't start another.
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#2" }], extData: { "uniq": r() } }, // overwriting
|
||||
@ -31,10 +42,10 @@ function test() {
|
||||
{ entries: [{ url: "http://example.org#6" }] } // creating
|
||||
], selected: 1 }] };
|
||||
|
||||
gProgressListener.setCallback(function progressCallback(aBrowser) {
|
||||
function progressCallback(aBrowser) {
|
||||
// We'll remove the progress listener after the first one because we aren't
|
||||
// loading any other tabs
|
||||
gProgressListener.unsetCallback();
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
|
||||
let curState = JSON.parse(ss.getBrowserState());
|
||||
for (let i = 0; i < curState.windows[0].tabs.length; i++) {
|
||||
@ -98,8 +109,9 @@ function test() {
|
||||
"(creating) new data is stored in extData where there was none");
|
||||
|
||||
cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ function firstOnLoad(aEvent) {
|
||||
ss.getBrowserState();
|
||||
|
||||
is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
|
||||
ok(tab.linkedBrowser.__SS_data, "newly created tab should be in save state");
|
||||
|
||||
tab.linkedBrowser.loadURI(URI_TO_LOAD);
|
||||
}
|
||||
|
@ -18,15 +18,19 @@ function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
TabsProgressListener.uninit();
|
||||
ss.setBrowserState(stateBackup);
|
||||
});
|
||||
|
||||
|
||||
TabsProgressListener.init();
|
||||
|
||||
window.addEventListener("SSWindowStateReady", function onReady() {
|
||||
window.removeEventListener("SSWindowStateReady", onReady, false);
|
||||
|
||||
let firstProgress = true;
|
||||
|
||||
gProgressListener.setCallback(function (browser, needsRestore, isRestoring) {
|
||||
TabsProgressListener.setCallback(function (needsRestore, isRestoring) {
|
||||
if (firstProgress) {
|
||||
firstProgress = false;
|
||||
is(isRestoring, 3, "restoring 3 tabs concurrently");
|
||||
@ -35,7 +39,7 @@ function test() {
|
||||
}
|
||||
|
||||
if (0 == needsRestore) {
|
||||
gProgressListener.unsetCallback();
|
||||
TabsProgressListener.unsetCallback();
|
||||
waitForFocus(finish);
|
||||
}
|
||||
});
|
||||
@ -45,3 +49,51 @@ function test() {
|
||||
|
||||
ss.setBrowserState(JSON.stringify(statePinned));
|
||||
}
|
||||
|
||||
function countTabs() {
|
||||
let needsRestore = 0, isRestoring = 0;
|
||||
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
let window = windowsEnum.getNext();
|
||||
if (window.closed)
|
||||
continue;
|
||||
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
let browser = window.gBrowser.tabs[i].linkedBrowser;
|
||||
if (browser.__SS_restoreState == TAB_STATE_RESTORING)
|
||||
isRestoring++;
|
||||
else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
|
||||
needsRestore++;
|
||||
}
|
||||
}
|
||||
|
||||
return [needsRestore, isRestoring];
|
||||
}
|
||||
|
||||
let TabsProgressListener = {
|
||||
init: function () {
|
||||
gBrowser.addTabsProgressListener(this);
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
this.unsetCallback();
|
||||
gBrowser.removeTabsProgressListener(this);
|
||||
},
|
||||
|
||||
setCallback: function (callback) {
|
||||
this.callback = callback;
|
||||
},
|
||||
|
||||
unsetCallback: function () {
|
||||
delete this.callback;
|
||||
},
|
||||
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (this.callback && aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
this.callback.apply(null, countTabs());
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ function test() {
|
||||
isnot(gBrowser.selectedTab, tab, "newly created tab is not selected");
|
||||
|
||||
ss.setTabState(tab, JSON.stringify(tabState));
|
||||
ok(SessionStore.isTabStateNeedsRestore(browser), "tab needs restoring");
|
||||
is(browser.__SS_restoreState, TAB_STATE_NEEDS_RESTORE, "tab needs restoring");
|
||||
|
||||
let state = JSON.parse(ss.getTabState(tab));
|
||||
let formdata = state.entries[0].formdata;
|
||||
|
@ -36,7 +36,8 @@ function runTests() {
|
||||
yield waitForInput();
|
||||
|
||||
// Check that we'll save the form data state correctly.
|
||||
let {entries: [{formdata}]} = JSON.parse(ss.getTabState(tab));
|
||||
let state = JSON.parse(ss.getBrowserState());
|
||||
let {formdata} = state.windows[0].tabs[1].entries[0];
|
||||
is(formdata.id.chk, true, "chk's value is correct");
|
||||
|
||||
// Clear dirty state of all windows again.
|
||||
@ -48,15 +49,11 @@ function runTests() {
|
||||
yield waitForInput();
|
||||
|
||||
// Check that we'll save the form data state correctly.
|
||||
let {entries: [{formdata}]} = JSON.parse(ss.getTabState(tab));
|
||||
let state = JSON.parse(ss.getBrowserState());
|
||||
let {formdata} = state.windows[0].tabs[1].entries[0];
|
||||
is(formdata.id.chk, true, "chk's value is correct");
|
||||
is(formdata.id.txt, "m", "txt's value is correct");
|
||||
|
||||
// Check that the form data cache works properly.
|
||||
let {entries: [{formdata}]} = JSON.parse(ss.getTabState(tab));
|
||||
is(formdata.id.chk, true, "chk's cached value is correct");
|
||||
is(formdata.id.txt, "m", "txt's cached value is correct");
|
||||
|
||||
// Clear dirty state of all windows again.
|
||||
yield forceWriteState();
|
||||
|
||||
@ -71,12 +68,8 @@ function runTests() {
|
||||
yield waitForInput();
|
||||
|
||||
// Check that we'll save the iframe's form data correctly.
|
||||
let {entries: [{children: [{formdata}]}]} = JSON.parse(ss.getTabState(tab));
|
||||
is(formdata.id.chk, true, "iframe chk's value is correct");
|
||||
is(formdata.id.txt, "m", "iframe txt's value is correct");
|
||||
|
||||
// Check that the form data cache works properly for subframes.
|
||||
let {entries: [{children: [{formdata}]}]} = JSON.parse(ss.getTabState(tab));
|
||||
let state = JSON.parse(ss.getBrowserState());
|
||||
let {formdata} = state.windows[0].tabs[1].entries[0].children[0];
|
||||
is(formdata.id.chk, true, "iframe chk's value is correct");
|
||||
is(formdata.id.txt, "m", "iframe txt's value is correct");
|
||||
|
||||
@ -88,8 +81,9 @@ function runTests() {
|
||||
EventUtils.synthesizeKey("m", {});
|
||||
yield waitForInput();
|
||||
|
||||
// Check that we'll correctly save the content editable's state.
|
||||
let {entries: [{children: [, {innerHTML}]}]} = JSON.parse(ss.getTabState(tab));
|
||||
// Check the we'll correctly save the content editable's state.
|
||||
let state = JSON.parse(ss.getBrowserState());
|
||||
let {innerHTML} = state.windows[0].tabs[1].entries[0].children[1];
|
||||
is(innerHTML, "m", "content editable's value is correct");
|
||||
|
||||
// Clean up after ourselves.
|
||||
|
@ -2,6 +2,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/. */
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
|
||||
let SessionStore = tmp.SessionStore;
|
||||
@ -238,7 +241,7 @@ let gProgressListener = {
|
||||
function gProgressListener_onStateChange(aBrowser, aWebProgress, aRequest,
|
||||
aStateFlags, aStatus) {
|
||||
if ((!this._checkRestoreState ||
|
||||
SessionStore.isTabStateRestoring(aBrowser)) &&
|
||||
aBrowser.__SS_restoreState == TAB_STATE_RESTORING) &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
|
||||
@ -253,9 +256,9 @@ let gProgressListener = {
|
||||
for (let win in BrowserWindowIterator()) {
|
||||
for (let i = 0; i < win.gBrowser.tabs.length; i++) {
|
||||
let browser = win.gBrowser.tabs[i].linkedBrowser;
|
||||
if (SessionStore.isTabStateRestoring(browser))
|
||||
if (browser.__SS_restoreState == TAB_STATE_RESTORING)
|
||||
isRestoring++;
|
||||
else if (SessionStore.isTabStateNeedsRestore(browser))
|
||||
else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
|
||||
needsRestore++;
|
||||
else
|
||||
wasRestored++;
|
||||
|
@ -3,6 +3,9 @@
|
||||
|
||||
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
let state = {windows:[{tabs:[
|
||||
@ -122,9 +125,9 @@ function countTabs() {
|
||||
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
let browser = window.gBrowser.tabs[i].linkedBrowser;
|
||||
if (SessionStore.isTabStateRestoring(browser))
|
||||
if (browser.__SS_restoreState == TAB_STATE_RESTORING)
|
||||
isRestoring++;
|
||||
else if (SessionStore.isTabStateNeedsRestore(browser))
|
||||
else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
|
||||
needsRestore++;
|
||||
}
|
||||
}
|
||||
@ -165,9 +168,8 @@ let TabsProgressListener = {
|
||||
self.callback.apply(null, countTabs());
|
||||
};
|
||||
|
||||
let isRestoring = SessionStore.isTabStateRestoring(aBrowser);
|
||||
let needsRestore = SessionStore.isTabStateNeedsRestore(aBrowser);
|
||||
let wasRestoring = !isRestoring && !needsRestore && aBrowser.__wasRestoring;
|
||||
let isRestoring = aBrowser.__SS_restoreState == TAB_STATE_RESTORING;
|
||||
let wasRestoring = !aBrowser.__SS_restoreState && aBrowser.__wasRestoring;
|
||||
let hasStopped = aStateFlags & Ci.nsIWebProgressListener.STATE_STOP;
|
||||
|
||||
if (isRestoring && !hasStopped)
|
||||
|
@ -1,10 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
|
||||
let SessionStore = tmp.SessionStore;
|
||||
|
||||
// Some tests here assume that all restored tabs are loaded without waiting for
|
||||
// the user to bring them to the foreground. We ensure this by resetting the
|
||||
// related preference (see the "firefox.js" defaults file for details).
|
||||
@ -121,6 +117,8 @@ function newWindowWithTabView(shownCallback, loadCallback, width, height) {
|
||||
|
||||
// ----------
|
||||
function afterAllTabsLoaded(callback, win) {
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
|
||||
win = win || window;
|
||||
|
||||
let stillToLoad = 0;
|
||||
@ -139,7 +137,8 @@ function afterAllTabsLoaded(callback, win) {
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
let isRestorable = !(tab.hidden && !restoreHiddenTabs &&
|
||||
SessionStore.isTabStateNeedsRestore(browser));
|
||||
browser.__SS_restoreState &&
|
||||
browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE);
|
||||
|
||||
if (isRestorable && browser.contentDocument.readyState != "complete" ||
|
||||
browser.webProgress.isLoadingDocument) {
|
||||
|
@ -19,6 +19,10 @@ Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/gcli.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
|
||||
var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
let telemetry = new Telemetry();
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
|
||||
"resource:///modules/devtools/gDevTools.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
|
||||
@ -1950,8 +1954,7 @@ gcli.addCommand({
|
||||
description: gcli.lookup('paintflashingToggleDesc'),
|
||||
manual: gcli.lookup('paintflashingManual'),
|
||||
exec: function(args, context) {
|
||||
var gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
|
||||
var window = gBrowser.contentWindow;
|
||||
var window = context.environment.window;
|
||||
var wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils);
|
||||
wUtils.paintFlashing = !wUtils.paintFlashing;
|
||||
@ -1970,6 +1973,15 @@ gcli.addCommand({
|
||||
var target = devtools.TargetFactory.forTab(tab);
|
||||
target.off("navigate", fireChange);
|
||||
target.once("navigate", fireChange);
|
||||
|
||||
var window = context.environment.window;
|
||||
var wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
if (wUtils.paintFlashing) {
|
||||
telemetry.toolOpened("paintflashing");
|
||||
} else {
|
||||
telemetry.toolClosed("paintflashing");
|
||||
}
|
||||
}
|
||||
}(this));
|
||||
|
||||
|
@ -15,6 +15,9 @@ const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this,
|
||||
"DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
|
||||
|
||||
@ -395,6 +398,7 @@ this.ChromeDebuggerProcess = function ChromeDebuggerProcess(aDebuggerUI, aOnClos
|
||||
this._win = aDebuggerUI.chromeWindow;
|
||||
this._closeCallback = aOnClose;
|
||||
this._runCallback = aOnRun;
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
this._initServer();
|
||||
this._initProfile();
|
||||
@ -477,6 +481,8 @@ ChromeDebuggerProcess.prototype = {
|
||||
process.runwAsync(args, args.length, { observe: this.close.bind(this) });
|
||||
this._dbgProcess = process;
|
||||
|
||||
this._telemetry.toolOpened("jsbrowserdebugger");
|
||||
|
||||
if (typeof this._runCallback == "function") {
|
||||
this._runCallback.call({}, this);
|
||||
}
|
||||
@ -487,6 +493,9 @@ ChromeDebuggerProcess.prototype = {
|
||||
*/
|
||||
close: function() {
|
||||
dumpn("Closing chrome debugging process");
|
||||
|
||||
this._telemetry.toolClosed("jsbrowserdebugger");
|
||||
|
||||
if (!this.globalUI) {
|
||||
dumpn("globalUI is missing");
|
||||
return;
|
||||
|
@ -17,7 +17,6 @@ function SourcesView() {
|
||||
this._onEditorUnload = this._onEditorUnload.bind(this);
|
||||
this._onEditorSelection = this._onEditorSelection.bind(this);
|
||||
this._onEditorContextMenu = this._onEditorContextMenu.bind(this);
|
||||
this._onSourceMouseDown = this._onSourceMouseDown.bind(this);
|
||||
this._onSourceSelect = this._onSourceSelect.bind(this);
|
||||
this._onSourceClick = this._onSourceClick.bind(this);
|
||||
this._onBreakpointClick = this._onBreakpointClick.bind(this);
|
||||
@ -48,7 +47,6 @@ create({ constructor: SourcesView, proto: MenuContainer.prototype }, {
|
||||
|
||||
window.addEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
|
||||
window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
|
||||
this.node.addEventListener("mousedown", this._onSourceMouseDown, false);
|
||||
this.node.addEventListener("select", this._onSourceSelect, false);
|
||||
this.node.addEventListener("click", this._onSourceClick, false);
|
||||
this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
|
||||
@ -69,7 +67,6 @@ create({ constructor: SourcesView, proto: MenuContainer.prototype }, {
|
||||
|
||||
window.removeEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
|
||||
window.removeEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
|
||||
this.node.removeEventListener("mousedown", this._onSourceMouseDown, false);
|
||||
this.node.removeEventListener("select", this._onSourceSelect, false);
|
||||
this.node.removeEventListener("click", this._onSourceClick, false);
|
||||
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
|
||||
@ -103,7 +100,7 @@ create({ constructor: SourcesView, proto: MenuContainer.prototype }, {
|
||||
* - forced: force the source to be immediately added
|
||||
*/
|
||||
addSource: function(aSource, aOptions = {}) {
|
||||
let url = aSource.url;
|
||||
let url = NetworkHelper.convertToUnicode(unescape(aSource.url));
|
||||
let label = SourceUtils.getSourceLabel(url.split(" -> ").pop());
|
||||
let group = SourceUtils.getSourceGroup(url.split(" -> ").pop());
|
||||
|
||||
@ -628,17 +625,6 @@ create({ constructor: SourcesView, proto: MenuContainer.prototype }, {
|
||||
this._editorContextMenuLineNumber = line;
|
||||
},
|
||||
|
||||
/**
|
||||
* The mouse down listener for the sources container.
|
||||
*/
|
||||
_onSourceMouseDown: function(e) {
|
||||
let item = this.getItemForElement(e.target);
|
||||
if (item) {
|
||||
// The container is not empty and we clicked on an actual item.
|
||||
this.selectedItem = item;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for the sources container.
|
||||
*/
|
||||
|
@ -1247,7 +1247,7 @@ FilterView.prototype = {
|
||||
*/
|
||||
_doVariablesFocus: function() {
|
||||
DebuggerView.showInstrumentsPane();
|
||||
DebuggerView.Variables.focusFirstVisibleNode();
|
||||
DebuggerView.Variables.focusFirstVisibleItem();
|
||||
},
|
||||
|
||||
_searchbox: null,
|
||||
@ -1362,7 +1362,7 @@ create({ constructor: FilteredSourcesView, proto: ResultsPanelContainer.prototyp
|
||||
onSelect: function(e) {
|
||||
let locationItem = this.getItemForElement(e.target);
|
||||
if (locationItem) {
|
||||
DebuggerView.Sources.selectedValue = locationItem.attachment.fullValue;
|
||||
DebuggerView.updateEditor(locationItem.attachment.fullValue, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -354,7 +354,9 @@ let DebuggerView = {
|
||||
}
|
||||
// If the requested source exists, display it and update.
|
||||
else if (this.Sources.containsValue(aUrl) && !aFlags.noSwitch) {
|
||||
this.Sources.node.preventFocusOnSelection = true;
|
||||
this.Sources.selectedValue = aUrl;
|
||||
this.Sources.node.preventFocusOnSelection = false;
|
||||
set(aLine);
|
||||
}
|
||||
// Dumb request, invalidate the caret position and debug location.
|
||||
@ -612,10 +614,9 @@ ListWidget.prototype = {
|
||||
removeAllItems: function() {
|
||||
let parent = this._parent;
|
||||
let list = this._list;
|
||||
let firstChild;
|
||||
|
||||
while ((firstChild = list.firstChild)) {
|
||||
list.removeChild(firstChild);
|
||||
while (list.hasChildNodes()) {
|
||||
list.firstChild.remove();
|
||||
}
|
||||
parent.scrollTop = 0;
|
||||
parent.scrollLeft = 0;
|
||||
@ -891,8 +892,7 @@ create({ constructor: ResultsPanelContainer, proto: MenuContainer.prototype }, {
|
||||
* @param string aDescription
|
||||
* An optional description of the item.
|
||||
*/
|
||||
_createItemView:
|
||||
function RPC__createItemView(aElementNode, aAttachment, aLabel, aValue, aDescription) {
|
||||
_createItemView: function(aElementNode, aAttachment, aLabel, aValue, aDescription) {
|
||||
let labelsGroup = document.createElement("hbox");
|
||||
if (aDescription) {
|
||||
let preLabelNode = document.createElement("label");
|
||||
|
@ -26,6 +26,10 @@
|
||||
<commandset id="sourceEditorCommands"/>
|
||||
|
||||
<commandset id="debuggerCommands">
|
||||
<command id="nextSourceCommand"
|
||||
oncommand="DebuggerView.Sources.selectNextItem()"/>
|
||||
<command id="prevSourceCommand"
|
||||
oncommand="DebuggerView.Sources.selectPrevItem()"/>
|
||||
<command id="resumeCommand"
|
||||
oncommand="DebuggerView.Toolbar._onResumePressed()"/>
|
||||
<command id="stepOverCommand"
|
||||
@ -171,6 +175,14 @@
|
||||
</popupset>
|
||||
|
||||
<keyset id="debuggerKeys">
|
||||
<key id="nextSourceKey"
|
||||
keycode="VK_DOWN"
|
||||
modifiers="accel alt"
|
||||
command="nextSourceCommand"/>
|
||||
<key id="prevSourceKey"
|
||||
keycode="VK_UP"
|
||||
modifiers="accel alt"
|
||||
command="prevSourceCommand"/>
|
||||
<key id="resumeKey"
|
||||
keycode="&debuggerUI.stepping.resume;"
|
||||
command="resumeCommand"/>
|
||||
|
@ -440,18 +440,35 @@ let gDevToolsBrowser = {
|
||||
|
||||
let amp = doc.getElementById("appmenu_webDeveloper_popup");
|
||||
if (amp) {
|
||||
let ref = (prevDef != null) ?
|
||||
doc.getElementById("appmenuitem_" + prevDef.id).nextSibling :
|
||||
doc.getElementById("appmenu_devtools_separator");
|
||||
let ref;
|
||||
|
||||
amp.insertBefore(elements.appmenuitem, ref);
|
||||
if (prevDef != null) {
|
||||
let menuitem = doc.getElementById("appmenuitem_" + prevDef.id);
|
||||
ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
|
||||
} else {
|
||||
ref = doc.getElementById("appmenu_devtools_separator");
|
||||
}
|
||||
|
||||
if (ref) {
|
||||
amp.insertBefore(elements.appmenuitem, ref);
|
||||
}
|
||||
}
|
||||
|
||||
let mp = doc.getElementById("menuWebDeveloperPopup");
|
||||
let ref = (prevDef != null) ?
|
||||
doc.getElementById("menuitem_" + prevDef.id).nextSibling :
|
||||
doc.getElementById("menu_devtools_separator");
|
||||
mp.insertBefore(elements.menuitem, ref);
|
||||
if (mp) {
|
||||
let ref;
|
||||
|
||||
if (prevDef != null) {
|
||||
let menuitem = doc.getElementById("menuitem_" + prevDef.id);
|
||||
ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
|
||||
} else {
|
||||
ref = doc.getElementById("menu_devtools_separator");
|
||||
}
|
||||
|
||||
if (ref) {
|
||||
mp.insertBefore(elements.menuitem, ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
var Promise = require("sdk/core/promise");
|
||||
var EventEmitter = require("devtools/shared/event-emitter");
|
||||
var Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
@ -28,6 +29,8 @@ function ToolSidebar(tabbox, panel, showTabstripe=true)
|
||||
this._panelDoc = this._tabbox.ownerDocument;
|
||||
this._toolPanel = panel;
|
||||
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
this._tabbox.tabpanels.addEventListener("select", this, true);
|
||||
|
||||
this._tabs = new Map();
|
||||
@ -137,9 +140,11 @@ ToolSidebar.prototype = {
|
||||
let previousTool = this._currentTool;
|
||||
this._currentTool = this.getCurrentTabID();
|
||||
if (previousTool) {
|
||||
this._telemetry.toolClosed(previousTool);
|
||||
this.emit(previousTool + "-unselected");
|
||||
}
|
||||
|
||||
this._telemetry.toolOpened(this._currentTool);
|
||||
this.emit(this._currentTool + "-selected");
|
||||
this.emit("select", this._currentTool);
|
||||
}
|
||||
@ -201,6 +206,10 @@ ToolSidebar.prototype = {
|
||||
this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
|
||||
}
|
||||
|
||||
if (this._currentTool) {
|
||||
this._telemetry.toolClosed(this._currentTool);
|
||||
}
|
||||
|
||||
this._tabs = null;
|
||||
this._tabbox = null;
|
||||
this._panelDoc = null;
|
||||
|
@ -55,7 +55,13 @@ function testMouseClicks() {
|
||||
}
|
||||
gDevTools.once("pref-changed", prefChanged);
|
||||
info("Click event synthesized for index " + index);
|
||||
EventUtils.synthesizeMouse(prefNodes[index], 10, 10, {}, panelWin);
|
||||
prefNodes[index].scrollIntoView();
|
||||
|
||||
// We use executeSoon here to ensure that the element is in view and
|
||||
// clickable.
|
||||
executeSoon(function() {
|
||||
EventUtils.synthesizeMouseAtCenter(prefNodes[index], {}, panelWin);
|
||||
});
|
||||
}
|
||||
|
||||
function prefChanged(event, data) {
|
||||
@ -89,11 +95,11 @@ function checkTools() {
|
||||
function toggleTools() {
|
||||
if (index < prefNodes.length) {
|
||||
gDevTools.once("tool-unregistered", checkUnregistered);
|
||||
EventUtils.synthesizeMouse(prefNodes[index], 10, 10, {}, panelWin);
|
||||
EventUtils.synthesizeMouseAtCenter(prefNodes[index], {}, panelWin);
|
||||
}
|
||||
else if (index < 2*prefNodes.length) {
|
||||
gDevTools.once("tool-registered", checkRegistered);
|
||||
EventUtils.synthesizeMouse(prefNodes[index - prefNodes.length], 10, 10, {}, panelWin);
|
||||
EventUtils.synthesizeMouseAtCenter(prefNodes[index - prefNodes.length], {}, panelWin);
|
||||
}
|
||||
else {
|
||||
cleanup();
|
||||
|
@ -8,6 +8,7 @@ const {Cc, Ci, Cu} = require("chrome");
|
||||
const MAX_ORDINAL = 99;
|
||||
let Promise = require("sdk/core/promise");
|
||||
let EventEmitter = require("devtools/shared/event-emitter");
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
@ -58,6 +59,7 @@ XPCOMUtils.defineLazyGetter(this, "Requisition", function() {
|
||||
function Toolbox(target, selectedTool, hostType) {
|
||||
this._target = target;
|
||||
this._toolPanels = new Map();
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
this._toolRegistered = this._toolRegistered.bind(this);
|
||||
this._toolUnregistered = this._toolUnregistered.bind(this);
|
||||
@ -191,8 +193,8 @@ Toolbox.prototype = {
|
||||
open: function TBOX_open() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
this._host.create().then(function(iframe) {
|
||||
let domReady = function() {
|
||||
this._host.create().then(iframe => {
|
||||
let domReady = () => {
|
||||
iframe.removeEventListener("DOMContentLoaded", domReady, true);
|
||||
|
||||
this.isReady = true;
|
||||
@ -206,15 +208,17 @@ Toolbox.prototype = {
|
||||
this._buildButtons();
|
||||
this._addKeysToWindow();
|
||||
|
||||
this._telemetry.toolOpened("toolbox");
|
||||
|
||||
this.selectTool(this._defaultToolId).then(function(panel) {
|
||||
this.emit("ready");
|
||||
deferred.resolve();
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
iframe.addEventListener("DOMContentLoaded", domReady, true);
|
||||
iframe.setAttribute("src", this._URL);
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
@ -327,9 +331,7 @@ Toolbox.prototype = {
|
||||
let buttons = CommandUtils.createButtons(toolbarSpec, this._target, this.doc, requisition);
|
||||
|
||||
let container = this.doc.getElementById("toolbox-buttons");
|
||||
buttons.forEach(function(button) {
|
||||
container.appendChild(button);
|
||||
}.bind(this));
|
||||
buttons.forEach(container.appendChild.bind(container));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -423,6 +425,8 @@ Toolbox.prototype = {
|
||||
let tab = this.doc.getElementById("toolbox-tab-" + id);
|
||||
tab.setAttribute("selected", "true");
|
||||
|
||||
let prevToolId = this._currentToolId;
|
||||
|
||||
if (this._currentToolId == id) {
|
||||
// Return the existing panel in order to have a consistent return value.
|
||||
return Promise.resolve(this._toolPanels.get(id));
|
||||
@ -433,7 +437,12 @@ Toolbox.prototype = {
|
||||
}
|
||||
let tab = this.doc.getElementById("toolbox-tab-" + id);
|
||||
|
||||
if (!tab) {
|
||||
if (tab) {
|
||||
if (prevToolId) {
|
||||
this._telemetry.toolClosed(prevToolId);
|
||||
}
|
||||
this._telemetry.toolOpened(id);
|
||||
} else {
|
||||
throw new Error("No tool found");
|
||||
}
|
||||
|
||||
@ -712,6 +721,8 @@ Toolbox.prototype = {
|
||||
|
||||
outstanding.push(this._host.destroy());
|
||||
|
||||
this._telemetry.destroy();
|
||||
|
||||
// Targets need to be notified that the toolbox is being torn down, so that
|
||||
// remote protocol connections can be gracefully terminated.
|
||||
if (this._target) {
|
||||
|
@ -243,13 +243,23 @@ TargetEventsHandler.prototype = {
|
||||
* Packet received from the server.
|
||||
*/
|
||||
_onTabNavigated: function(aType, aPacket) {
|
||||
if (aType == "will-navigate") {
|
||||
NetMonitorView.RequestsMenu.reset();
|
||||
NetMonitorView.NetworkDetails.reset();
|
||||
window.emit("NetMonitor:TargetWillNavigate");
|
||||
}
|
||||
if (aType == "navigate") {
|
||||
window.emit("NetMonitor:TargetNavigate");
|
||||
switch (aType) {
|
||||
case "will-navigate": {
|
||||
// Reset UI.
|
||||
NetMonitorView.RequestsMenu.reset();
|
||||
NetMonitorView.NetworkDetails.reset();
|
||||
|
||||
// Reset global helpers cache.
|
||||
nsIURL.store.clear();
|
||||
drain.store.clear();
|
||||
|
||||
window.emit("NetMonitor:TargetWillNavigate");
|
||||
break;
|
||||
}
|
||||
case "navigate": {
|
||||
window.emit("NetMonitor:TargetNavigate");
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -310,8 +320,8 @@ NetworkEventsHandler.prototype = {
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onNetworkEvent: function(aType, aPacket) {
|
||||
let { actor, startedDateTime, method, url } = aPacket.eventActor;
|
||||
NetMonitorView.RequestsMenu.addRequest(actor, startedDateTime, method, url);
|
||||
let { actor, startedDateTime, method, url, isXHR } = aPacket.eventActor;
|
||||
NetMonitorView.RequestsMenu.addRequest(actor, startedDateTime, method, url, isXHR);
|
||||
|
||||
window.emit("NetMonitor:NetworkEvent");
|
||||
},
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const EPSILON = 0.001;
|
||||
const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400; // 100 KB in bytes
|
||||
const RESIZE_REFRESH_RATE = 50; // ms
|
||||
const REQUESTS_REFRESH_RATE = 50; // ms
|
||||
const REQUESTS_HEADERS_SAFE_BOUNDS = 30; // px
|
||||
@ -103,6 +104,7 @@ let NetMonitorView = {
|
||||
_initializePanes: function() {
|
||||
dumpn("Initializing the NetMonitorView panes");
|
||||
|
||||
this._body = $("#body");
|
||||
this._detailsPane = $("#details-pane");
|
||||
this._detailsPaneToggleButton = $("#details-pane-toggle");
|
||||
|
||||
@ -153,9 +155,11 @@ let NetMonitorView = {
|
||||
ViewHelpers.togglePane(aFlags, pane);
|
||||
|
||||
if (aFlags.visible) {
|
||||
this._body.removeAttribute("pane-collapsed");
|
||||
button.removeAttribute("pane-collapsed");
|
||||
button.setAttribute("tooltiptext", this._collapsePaneString);
|
||||
} else {
|
||||
this._body.setAttribute("pane-collapsed", "");
|
||||
button.setAttribute("pane-collapsed", "");
|
||||
button.setAttribute("tooltiptext", this._expandPaneString);
|
||||
}
|
||||
@ -190,9 +194,12 @@ let NetMonitorView = {
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
_editorPromises: new Map(),
|
||||
_body: null,
|
||||
_detailsPane: null,
|
||||
_detailsPaneToggleButton: null,
|
||||
_collapsePaneString: "",
|
||||
_expandPaneString: "",
|
||||
_editorPromises: new Map(),
|
||||
_isInitialized: false,
|
||||
_isDestroyed: false
|
||||
};
|
||||
@ -231,16 +238,14 @@ ToolbarView.prototype = {
|
||||
*/
|
||||
_onTogglePanesPressed: function() {
|
||||
let requestsMenu = NetMonitorView.RequestsMenu;
|
||||
let networkDetails = NetMonitorView.NetworkDetails;
|
||||
let selectedIndex = requestsMenu.selectedIndex;
|
||||
|
||||
// Make sure there's a selection if the button is pressed, to avoid
|
||||
// showing an empty network details pane.
|
||||
if (!requestsMenu.selectedItem && requestsMenu.itemCount) {
|
||||
if (selectedIndex == -1 && requestsMenu.itemCount) {
|
||||
requestsMenu.selectedIndex = 0;
|
||||
}
|
||||
// Proceed with toggling the network details pane normally.
|
||||
else {
|
||||
networkDetails.toggle(NetMonitorView.detailsPaneHidden);
|
||||
} else {
|
||||
requestsMenu.selectedIndex = -1;
|
||||
}
|
||||
},
|
||||
|
||||
@ -258,9 +263,11 @@ function RequestsMenuView() {
|
||||
this._cache = new Map(); // Can't use a WeakMap because keys are strings.
|
||||
this._flushRequests = this._flushRequests.bind(this);
|
||||
this._onRequestItemRemoved = this._onRequestItemRemoved.bind(this);
|
||||
this._onMouseDown = this._onMouseDown.bind(this);
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
this._byFile = this._byFile.bind(this);
|
||||
this._byDomain = this._byDomain.bind(this);
|
||||
this._byType = this._byType.bind(this);
|
||||
}
|
||||
|
||||
create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
@ -274,7 +281,6 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
this.node.maintainSelectionVisible = false;
|
||||
this.node.autoscrollWithAppendedItems = true;
|
||||
|
||||
this.node.addEventListener("mousedown", this._onMouseDown, false);
|
||||
this.node.addEventListener("select", this._onSelect, false);
|
||||
window.addEventListener("resize", this._onResize, false);
|
||||
},
|
||||
@ -285,7 +291,6 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
destroy: function() {
|
||||
dumpn("Destroying the SourcesView");
|
||||
|
||||
this.node.removeEventListener("mousedown", this._onMouseDown, false);
|
||||
this.node.removeEventListener("select", this._onSelect, false);
|
||||
window.removeEventListener("resize", this._onResize, false);
|
||||
},
|
||||
@ -316,8 +321,10 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
* Specifies the request method (e.g. "GET", "POST", etc.)
|
||||
* @param string aUrl
|
||||
* Specifies the request's url.
|
||||
* @param boolean aIsXHR
|
||||
* True if this request was initiated via XHR.
|
||||
*/
|
||||
addRequest: function(aId, aStartedDateTime, aMethod, aUrl) {
|
||||
addRequest: function(aId, aStartedDateTime, aMethod, aUrl, aIsXHR) {
|
||||
// Convert the received date/time string to a unix timestamp.
|
||||
let unixTime = Date.parse(aStartedDateTime);
|
||||
|
||||
@ -335,22 +342,75 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
|
||||
startedMillis: unixTime,
|
||||
method: aMethod,
|
||||
url: aUrl
|
||||
url: aUrl,
|
||||
isXHR: aIsXHR
|
||||
},
|
||||
finalize: this._onRequestItemRemoved
|
||||
});
|
||||
|
||||
$("#details-pane-toggle").disabled = false;
|
||||
$(".requests-menu-empty-notice").hidden = true;
|
||||
$("#requests-menu-empty-notice").hidden = true;
|
||||
|
||||
this._cache.set(aId, requestItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters all network requests in this container by a specified type.
|
||||
*
|
||||
* @param string aType
|
||||
* Either null, "html", "css", "js", "xhr", "fonts", "images", "media"
|
||||
* or "flash".
|
||||
*/
|
||||
filterOn: function(aType) {
|
||||
let target = $("#requests-menu-filter-" + aType + "-button");
|
||||
let buttons = document.querySelectorAll(".requests-menu-footer-button");
|
||||
|
||||
for (let button of buttons) {
|
||||
if (button != target) {
|
||||
button.removeAttribute("checked");
|
||||
} else {
|
||||
button.setAttribute("checked", "");
|
||||
}
|
||||
}
|
||||
|
||||
// Filter on nothing.
|
||||
if (!target) {
|
||||
this.filterContents(() => true);
|
||||
}
|
||||
// Filter on whatever was requested.
|
||||
else switch (aType) {
|
||||
case "html":
|
||||
this.filterContents(this._onHtml);
|
||||
break;
|
||||
case "css":
|
||||
this.filterContents(this._onCss);
|
||||
break;
|
||||
case "js":
|
||||
this.filterContents(this._onJs);
|
||||
break;
|
||||
case "xhr":
|
||||
this.filterContents(this._onXhr);
|
||||
break;
|
||||
case "fonts":
|
||||
this.filterContents(this._onFonts);
|
||||
break;
|
||||
case "images":
|
||||
this.filterContents(this._onImages);
|
||||
break;
|
||||
case "media":
|
||||
this.filterContents(this._onMedia);
|
||||
break;
|
||||
case "flash":
|
||||
this.filterContents(this._onFlash);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sorts all network requests in this container by a specified detail.
|
||||
*
|
||||
* @param string aType
|
||||
* Either "status", "method", "file", "domain", "type" or "size".
|
||||
* Either null, "status", "method", "file", "domain", "type" or "size".
|
||||
*/
|
||||
sortBy: function(aType) {
|
||||
let target = $("#requests-menu-" + aType + "-button");
|
||||
@ -428,6 +488,54 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Predicates used when filtering items.
|
||||
*
|
||||
* @param MenuItem aItem
|
||||
* The filtered menu item.
|
||||
* @return boolean
|
||||
* True if the menu item should be visible, false otherwise.
|
||||
*/
|
||||
_onHtml: function({ attachment: { mimeType } })
|
||||
mimeType && mimeType.contains("/html"),
|
||||
|
||||
_onCss: function({ attachment: { mimeType } })
|
||||
mimeType && mimeType.contains("/css"),
|
||||
|
||||
_onJs: function({ attachment: { mimeType } })
|
||||
mimeType && (
|
||||
mimeType.contains("/ecmascript") ||
|
||||
mimeType.contains("/javascript") ||
|
||||
mimeType.contains("/x-javascript")),
|
||||
|
||||
_onXhr: function({ attachment: { isXHR } })
|
||||
isXHR,
|
||||
|
||||
_onFonts: function({ attachment: { url, mimeType } }) // Fonts are a mess.
|
||||
(mimeType && (
|
||||
mimeType.contains("font/") ||
|
||||
mimeType.contains("/font"))) ||
|
||||
url.contains(".eot") ||
|
||||
url.contains(".ttf") ||
|
||||
url.contains(".otf") ||
|
||||
url.contains(".woff"),
|
||||
|
||||
_onImages: function({ attachment: { mimeType } })
|
||||
mimeType && mimeType.contains("image/"),
|
||||
|
||||
_onMedia: function({ attachment: { mimeType } }) // Not including images.
|
||||
mimeType && (
|
||||
mimeType.contains("audio/") ||
|
||||
mimeType.contains("video/") ||
|
||||
mimeType.contains("model/")),
|
||||
|
||||
_onFlash: function({ attachment: { url, mimeType } }) // Flash is a mess.
|
||||
(mimeType && (
|
||||
mimeType.contains("/x-flv") ||
|
||||
mimeType.contains("/x-shockwave-flash"))) ||
|
||||
url.contains(".swf") ||
|
||||
url.contains(".flv"),
|
||||
|
||||
/**
|
||||
* Predicates used when sorting items.
|
||||
*
|
||||
@ -440,32 +548,29 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
* 0 to leave aFirst and aSecond unchanged with respect to each other
|
||||
* 1 to sort aSecond to a lower index than aFirst
|
||||
*/
|
||||
_byTiming: (aFirst, aSecond) =>
|
||||
aFirst.attachment.startedMillis > aSecond.attachment.startedMillis,
|
||||
_byTiming: function({ attachment: first }, { attachment: second })
|
||||
first.startedMillis > second.startedMillis,
|
||||
|
||||
_byStatus: (aFirst, aSecond) =>
|
||||
aFirst.attachment.status > aSecond.attachment.status,
|
||||
_byStatus: function({ attachment: first }, { attachment: second })
|
||||
first.status > second.status,
|
||||
|
||||
_byMethod: (aFirst, aSecond) =>
|
||||
aFirst.attachment.method > aSecond.attachment.method,
|
||||
_byMethod: function({ attachment: first }, { attachment: second })
|
||||
first.method > second.method,
|
||||
|
||||
_byFile: (aFirst, aSecond) =>
|
||||
!aFirst.target || !aSecond.target ? -1 :
|
||||
$(".requests-menu-file", aFirst.target).getAttribute("value").toLowerCase() >
|
||||
$(".requests-menu-file", aSecond.target).getAttribute("value").toLowerCase(),
|
||||
_byFile: function({ attachment: first }, { attachment: second })
|
||||
this._getUriNameWithQuery(first.url).toLowerCase() >
|
||||
this._getUriNameWithQuery(second.url).toLowerCase(),
|
||||
|
||||
_byDomain: (aFirst, aSecond) =>
|
||||
!aFirst.target || !aSecond.target ? -1 :
|
||||
$(".requests-menu-domain", aFirst.target).getAttribute("value").toLowerCase() >
|
||||
$(".requests-menu-domain", aSecond.target).getAttribute("value").toLowerCase(),
|
||||
_byDomain: function({ attachment: first }, { attachment: second })
|
||||
this._getUriHostPort(first.url).toLowerCase() >
|
||||
this._getUriHostPort(second.url).toLowerCase(),
|
||||
|
||||
_byType: (aFirst, aSecond) =>
|
||||
!aFirst.target || !aSecond.target ? -1 :
|
||||
$(".requests-menu-type", aFirst.target).getAttribute("value").toLowerCase() >
|
||||
$(".requests-menu-type", aSecond.target).getAttribute("value").toLowerCase(),
|
||||
_byType: function({ attachment: first }, { attachment: second })
|
||||
this._getAbbreviatedMimeType(first.mimeType).toLowerCase() >
|
||||
this._getAbbreviatedMimeType(second.mimeType).toLowerCase(),
|
||||
|
||||
_bySize: (aFirst, aSecond) =>
|
||||
aFirst.attachment.contentSize > aSecond.attachment.contentSize,
|
||||
_bySize: function({ attachment: first }, { attachment: second })
|
||||
first.contentSize > second.contentSize,
|
||||
|
||||
/**
|
||||
* Schedules adding additional information to a network request.
|
||||
@ -579,8 +684,13 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
// We're done flushing all the requests, clear the update queue.
|
||||
this._updateQueue = [];
|
||||
|
||||
// Make sure all the requests are sorted.
|
||||
// Make sure all the requests are sorted and filtered.
|
||||
// Freshly added requests may not yet contain all the information required
|
||||
// for sorting and filtering predicates, so this is done each time the
|
||||
// network requests table is flushed (don't worry, events are drained first
|
||||
// so this doesn't happen once per network event update).
|
||||
this.sortContents();
|
||||
this.filterContents();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -594,24 +704,27 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
* The network request view.
|
||||
*/
|
||||
_createMenuView: function(aMethod, aUrl) {
|
||||
let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
|
||||
let name = NetworkHelper.convertToUnicode(unescape(uri.fileName)) || "/";
|
||||
let query = NetworkHelper.convertToUnicode(unescape(uri.query));
|
||||
let hostPort = NetworkHelper.convertToUnicode(unescape(uri.hostPort));
|
||||
let uri = nsIURL(aUrl);
|
||||
let nameWithQuery = this._getUriNameWithQuery(uri);
|
||||
let hostPort = this._getUriHostPort(uri);
|
||||
|
||||
let template = $("#requests-menu-item-template");
|
||||
let fragment = document.createDocumentFragment();
|
||||
|
||||
$(".requests-menu-method", template).setAttribute("value", aMethod);
|
||||
let method = $(".requests-menu-method", template);
|
||||
method.setAttribute("value", aMethod);
|
||||
|
||||
let file = $(".requests-menu-file", template);
|
||||
file.setAttribute("value", name + (query ? "?" + query : ""));
|
||||
file.setAttribute("tooltiptext", name + (query ? "?" + query : ""));
|
||||
file.setAttribute("value", nameWithQuery);
|
||||
file.setAttribute("tooltiptext", nameWithQuery);
|
||||
|
||||
let domain = $(".requests-menu-domain", template);
|
||||
domain.setAttribute("value", hostPort);
|
||||
domain.setAttribute("tooltiptext", hostPort);
|
||||
|
||||
let waterfall = $(".requests-menu-waterfall", template);
|
||||
waterfall.style.backgroundImage = this._cachedWaterfallBackground;
|
||||
|
||||
// Flatten the DOM by removing one redundant box (the template container).
|
||||
for (let node of template.childNodes) {
|
||||
fragment.appendChild(node.cloneNode(true));
|
||||
@ -652,7 +765,7 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
break;
|
||||
}
|
||||
case "mimeType": {
|
||||
let type = aValue.split(";")[0].split("/")[1] || "?";
|
||||
let type = this._getAbbreviatedMimeType(aValue);
|
||||
let node = $(".requests-menu-type", aItem.target);
|
||||
let text = CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type;
|
||||
node.setAttribute("value", text);
|
||||
@ -923,23 +1036,16 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
this._cache.delete(aItem.attachment.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* The mouse down listener for this container.
|
||||
*/
|
||||
_onMouseDown: function(e) {
|
||||
let item = this.getItemForElement(e.target);
|
||||
if (item) {
|
||||
// The container is not empty and we clicked on an actual item.
|
||||
this.selectedItem = item;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The selection listener for this container.
|
||||
*/
|
||||
_onSelect: function(e) {
|
||||
NetMonitorView.NetworkDetails.populate(this.selectedItem.attachment);
|
||||
NetMonitorView.NetworkDetails.toggle(true);
|
||||
_onSelect: function({ detail: item }) {
|
||||
if (item) {
|
||||
NetMonitorView.NetworkDetails.populate(item.attachment);
|
||||
NetMonitorView.NetworkDetails.toggle(true);
|
||||
} else {
|
||||
NetMonitorView.NetworkDetails.toggle(false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -976,6 +1082,40 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helpers for getting details about an nsIURL.
|
||||
*
|
||||
* @param nsIURL | string aUrl
|
||||
* @return string
|
||||
*/
|
||||
_getUriNameWithQuery: function(aUrl) {
|
||||
if (!(aUrl instanceof Ci.nsIURL)) {
|
||||
aUrl = nsIURL(aUrl);
|
||||
}
|
||||
let name = NetworkHelper.convertToUnicode(unescape(aUrl.fileName)) || "/";
|
||||
let query = NetworkHelper.convertToUnicode(unescape(aUrl.query));
|
||||
return name + (query ? "?" + query : "");
|
||||
},
|
||||
_getUriHostPort: function(aUrl) {
|
||||
if (!(aUrl instanceof Ci.nsIURL)) {
|
||||
aUrl = nsIURL(aUrl);
|
||||
}
|
||||
return NetworkHelper.convertToUnicode(unescape(aUrl.hostPort));
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper for getting an abbreviated string for a mime type.
|
||||
*
|
||||
* @param string aMimeType
|
||||
* @return string
|
||||
*/
|
||||
_getAbbreviatedMimeType: function(aMimeType) {
|
||||
if (!aMimeType) {
|
||||
return "";
|
||||
}
|
||||
return (aMimeType.split(";")[0].split("/")[1] || "").split("+")[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the available waterfall width in this container.
|
||||
* @return number
|
||||
@ -995,7 +1135,7 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
|
||||
_canvas: null,
|
||||
_ctx: null,
|
||||
_cachedWaterfallWidth: 0,
|
||||
_cachedWaterfallBackground: null,
|
||||
_cachedWaterfallBackground: "",
|
||||
_firstRequestStartedMillis: -1,
|
||||
_lastRequestEndedMillis: -1,
|
||||
_updateQueue: [],
|
||||
@ -1012,7 +1152,7 @@ function NetworkDetailsView() {
|
||||
this._onTabSelect = this._onTabSelect.bind(this);
|
||||
};
|
||||
|
||||
create({ constructor: NetworkDetailsView, proto: MenuContainer.prototype }, {
|
||||
NetworkDetailsView.prototype = {
|
||||
/**
|
||||
* Initialization function, called when the network monitor is started.
|
||||
*/
|
||||
@ -1291,8 +1431,7 @@ create({ constructor: NetworkDetailsView, proto: MenuContainer.prototype }, {
|
||||
* The request's url.
|
||||
*/
|
||||
_setRequestGetParams: function(aUrl) {
|
||||
let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
|
||||
let query = uri.query;
|
||||
let query = nsIURL(aUrl).query;
|
||||
if (query) {
|
||||
this._addParams(this._paramsQueryString, query);
|
||||
}
|
||||
@ -1374,7 +1513,6 @@ create({ constructor: NetworkDetailsView, proto: MenuContainer.prototype }, {
|
||||
if (!aResponse) {
|
||||
return;
|
||||
}
|
||||
let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
|
||||
let { mimeType, text, encoding } = aResponse.content;
|
||||
|
||||
gNetwork.getString(text).then((aString) => {
|
||||
@ -1403,7 +1541,7 @@ create({ constructor: NetworkDetailsView, proto: MenuContainer.prototype }, {
|
||||
|
||||
// Immediately display additional information about the image:
|
||||
// file name, mime type and encoding.
|
||||
$("#response-content-image-name-value").setAttribute("value", uri.fileName);
|
||||
$("#response-content-image-name-value").setAttribute("value", nsIURL(aUrl).fileName);
|
||||
$("#response-content-image-mime-value").setAttribute("value", mimeType);
|
||||
$("#response-content-image-encoding-value").setAttribute("value", encoding);
|
||||
|
||||
@ -1422,13 +1560,16 @@ create({ constructor: NetworkDetailsView, proto: MenuContainer.prototype }, {
|
||||
$("#response-content-textarea-box").hidden = false;
|
||||
NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
|
||||
aEditor.setMode(SourceEditor.MODES.TEXT);
|
||||
aEditor.setText(NetworkHelper.convertToUnicode(aString, "UTF-8"));
|
||||
aEditor.setText(aString);
|
||||
|
||||
// Maybe set a more appropriate mode in the Source Editor if possible.
|
||||
for (let key in CONTENT_MIME_TYPE_MAPPINGS) {
|
||||
if (mimeType.contains(key)) {
|
||||
aEditor.setMode(CONTENT_MIME_TYPE_MAPPINGS[key]);
|
||||
break;
|
||||
// Maybe set a more appropriate mode in the Source Editor if possible,
|
||||
// but avoid doing this for very large files.
|
||||
if (aString.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
|
||||
for (let key in CONTENT_MIME_TYPE_MAPPINGS) {
|
||||
if (mimeType.contains(key)) {
|
||||
aEditor.setMode(CONTENT_MIME_TYPE_MAPPINGS[key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1518,22 +1659,34 @@ create({ constructor: NetworkDetailsView, proto: MenuContainer.prototype }, {
|
||||
_responseHeaders: "",
|
||||
_requestCookies: "",
|
||||
_responseCookies: ""
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* DOM query helper.
|
||||
*/
|
||||
function $(aSelector, aTarget = document) aTarget.querySelector(aSelector);
|
||||
|
||||
/**
|
||||
* Helper for getting an nsIURL instance out of a string.
|
||||
*/
|
||||
function nsIURL(aUrl, aStore = nsIURL.store) {
|
||||
if (aStore.has(aUrl)) {
|
||||
return aStore.get(aUrl);
|
||||
}
|
||||
let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
|
||||
aStore.set(aUrl, uri);
|
||||
return uri;
|
||||
}
|
||||
nsIURL.store = new Map();
|
||||
|
||||
/**
|
||||
* Helper for draining a rapid succession of events and invoking a callback
|
||||
* once everything settles down.
|
||||
*/
|
||||
function drain(aId, aWait, aCallback) {
|
||||
window.clearTimeout(drain.store.get(aId));
|
||||
drain.store.set(aId, window.setTimeout(aCallback, aWait));
|
||||
function drain(aId, aWait, aCallback, aStore = drain.store) {
|
||||
window.clearTimeout(aStore.get(aId));
|
||||
aStore.set(aId, window.setTimeout(aCallback, aWait));
|
||||
}
|
||||
|
||||
drain.store = new Map();
|
||||
|
||||
/**
|
||||
|
@ -20,12 +20,23 @@
|
||||
#toolbar-spacer,
|
||||
#details-pane-toggle,
|
||||
#details-pane[pane-collapsed],
|
||||
.requests-menu-waterfall {
|
||||
.requests-menu-waterfall,
|
||||
.requests-menu-footer-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 701px) and (max-width: 1024px) {
|
||||
#body:not([pane-collapsed]) #requests-menu-footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 701px) {
|
||||
#requests-menu-spacer-start {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#network-table[waterfall-overflows] .requests-menu-waterfall {
|
||||
display: none;
|
||||
}
|
||||
|
@ -17,7 +17,9 @@
|
||||
<script type="text/javascript" src="netmonitor-controller.js"/>
|
||||
<script type="text/javascript" src="netmonitor-view.js"/>
|
||||
|
||||
<box id="body" flex="1" class="devtools-responsive-container">
|
||||
<box id="body"
|
||||
class="devtools-responsive-container"
|
||||
flex="1">
|
||||
<vbox id="network-table" flex="1">
|
||||
<toolbar id="requests-menu-toolbar"
|
||||
class="devtools-toolbar"
|
||||
@ -28,14 +30,14 @@
|
||||
align="center">
|
||||
<button id="requests-menu-status-button"
|
||||
class="requests-menu-header-button requests-menu-status"
|
||||
onclick="NetMonitorView.RequestsMenu.sortBy('status')">
|
||||
&netmonitorUI.toolbar.status;
|
||||
onclick="NetMonitorView.RequestsMenu.sortBy('status')"
|
||||
label="&netmonitorUI.toolbar.status;">
|
||||
</button>
|
||||
<button id="requests-menu-method-button"
|
||||
class="requests-menu-header-button requests-menu-method"
|
||||
onclick="NetMonitorView.RequestsMenu.sortBy('method')"
|
||||
label="&netmonitorUI.toolbar.method;"
|
||||
flex="1">
|
||||
&netmonitorUI.toolbar.method;
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-file-header-box"
|
||||
@ -44,8 +46,8 @@
|
||||
<button id="requests-menu-file-button"
|
||||
class="requests-menu-header-button requests-menu-file"
|
||||
onclick="NetMonitorView.RequestsMenu.sortBy('file')"
|
||||
label="&netmonitorUI.toolbar.file;"
|
||||
flex="1">
|
||||
&netmonitorUI.toolbar.file;
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-domain-header-box"
|
||||
@ -54,8 +56,8 @@
|
||||
<button id="requests-menu-domain-button"
|
||||
class="requests-menu-header-button requests-menu-domain"
|
||||
onclick="NetMonitorView.RequestsMenu.sortBy('domain')"
|
||||
label="&netmonitorUI.toolbar.domain;"
|
||||
flex="1">
|
||||
&netmonitorUI.toolbar.domain;
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-type-header-box"
|
||||
@ -64,8 +66,8 @@
|
||||
<button id="requests-menu-type-button"
|
||||
class="requests-menu-header-button requests-menu-type"
|
||||
onclick="NetMonitorView.RequestsMenu.sortBy('type')"
|
||||
label="&netmonitorUI.toolbar.type;"
|
||||
flex="1">
|
||||
&netmonitorUI.toolbar.type;
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-size-header-box"
|
||||
@ -74,8 +76,8 @@
|
||||
<button id="requests-menu-size-button"
|
||||
class="requests-menu-header-button requests-menu-size"
|
||||
onclick="NetMonitorView.RequestsMenu.sortBy('size')"
|
||||
label="&netmonitorUI.toolbar.size;"
|
||||
flex="1">
|
||||
&netmonitorUI.toolbar.size;
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-waterfall-header-box"
|
||||
@ -93,7 +95,7 @@
|
||||
disabled="true"
|
||||
tabindex="0"/>
|
||||
</toolbar>
|
||||
<label class="plain requests-menu-empty-notice"
|
||||
<label id="requests-menu-empty-notice"
|
||||
value="&netmonitorUI.emptyNotice;"/>
|
||||
<vbox id="requests-menu-contents" flex="1">
|
||||
<hbox id="requests-menu-item-template" hidden="true">
|
||||
@ -124,11 +126,66 @@
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<hbox id="requests-menu-footer">
|
||||
<spacer id="requests-menu-spacer-start"
|
||||
class="requests-menu-footer-spacer"
|
||||
flex="100"/>
|
||||
<button id="requests-menu-filter-all-button"
|
||||
class="requests-menu-footer-button"
|
||||
onclick="NetMonitorView.RequestsMenu.filterOn(null)"
|
||||
label="&netmonitorUI.footer.filterAll;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-html-button"
|
||||
class="requests-menu-footer-button"
|
||||
onclick="NetMonitorView.RequestsMenu.filterOn('html')"
|
||||
label="&netmonitorUI.footer.filterHTML;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-css-button"
|
||||
class="requests-menu-footer-button"
|
||||
onclick="NetMonitorView.RequestsMenu.filterOn('css')"
|
||||
label="&netmonitorUI.footer.filterCSS;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-js-button"
|
||||
class="requests-menu-footer-button"
|
||||
onclick="NetMonitorView.RequestsMenu.filterOn('js')"
|
||||
label="&netmonitorUI.footer.filterJS;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-xhr-button"
|
||||
class="requests-menu-footer-button"
|
||||
onclick="NetMonitorView.RequestsMenu.filterOn('xhr')"
|
||||
label="&netmonitorUI.footer.filterXHR;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-fonts-button"
|
||||
class="requests-menu-footer-button"
|
||||
onclick="NetMonitorView.RequestsMenu.filterOn('fonts')"
|
||||
label="&netmonitorUI.footer.filterFonts;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-images-button"
|
||||
class="requests-menu-footer-button"
|
||||
onclick="NetMonitorView.RequestsMenu.filterOn('images')"
|
||||
label="&netmonitorUI.footer.filterImages;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-media-button"
|
||||
class="requests-menu-footer-button"
|
||||
onclick="NetMonitorView.RequestsMenu.filterOn('media')"
|
||||
label="&netmonitorUI.footer.filterMedia;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-flash-button"
|
||||
class="requests-menu-footer-button"
|
||||
onclick="NetMonitorView.RequestsMenu.filterOn('flash')"
|
||||
label="&netmonitorUI.footer.filterFlash;">
|
||||
</button>
|
||||
<spacer id="requests-menu-spacer-end"
|
||||
class="requests-menu-footer-spacer"
|
||||
flex="100"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
|
||||
<tabbox id="details-pane" class="devtools-sidebar-tabs" hidden="true">
|
||||
<tabbox id="details-pane"
|
||||
class="devtools-sidebar-tabs"
|
||||
hidden="true">
|
||||
<tabs>
|
||||
<tab label="&netmonitorUI.tab.headers;"/>
|
||||
<tab label="&netmonitorUI.tab.cookies;"/>
|
||||
@ -166,7 +223,7 @@
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.summary.status;"/>
|
||||
<box id="headers-summary-status-circle"
|
||||
class="requests-menu-status"/>
|
||||
class="requests-menu-status"/>
|
||||
<label id="headers-summary-status-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
|
@ -18,11 +18,14 @@ MOCHITEST_BROWSER_TESTS = \
|
||||
browser_net_prefs-and-l10n.js \
|
||||
browser_net_prefs-reload.js \
|
||||
browser_net_pane-collapse.js \
|
||||
browser_net_pane-toggle.js \
|
||||
browser_net_simple-request.js \
|
||||
browser_net_simple-request-data.js \
|
||||
browser_net_simple-request-details.js \
|
||||
browser_net_content-type.js \
|
||||
browser_net_cyrillic.js \
|
||||
browser_net_cyrillic-01.js \
|
||||
browser_net_cyrillic-02.js \
|
||||
browser_net_large-response.js \
|
||||
browser_net_status-codes.js \
|
||||
browser_net_post-data.js \
|
||||
browser_net_jsonp.js \
|
||||
@ -31,6 +34,11 @@ MOCHITEST_BROWSER_TESTS = \
|
||||
browser_net_sort-01.js \
|
||||
browser_net_sort-02.js \
|
||||
browser_net_sort-03.js \
|
||||
browser_net_filter-01.js \
|
||||
browser_net_filter-02.js \
|
||||
browser_net_filter-03.js \
|
||||
browser_net_accessibility-01.js \
|
||||
browser_net_accessibility-02.js \
|
||||
head.js \
|
||||
$(NULL)
|
||||
|
||||
@ -45,7 +53,9 @@ MOCHITEST_BROWSER_PAGES = \
|
||||
html_jsonp-test-page.html \
|
||||
html_json-long-test-page.html \
|
||||
html_sorting-test-page.html \
|
||||
html_filter-test-page.html \
|
||||
html_infinite-get-page.html \
|
||||
html_custom-get-page.html \
|
||||
sjs_simple-test-server.sjs \
|
||||
sjs_content-type-test-server.sjs \
|
||||
sjs_status-codes-test-server.sjs \
|
||||
|
@ -0,0 +1,80 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if focus modifiers work for the SideMenuWidget.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
initNetMonitor(CUSTOM_GET_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 2).then(() => {
|
||||
check(-1, false);
|
||||
|
||||
RequestsMenu.focusLastVisibleItem();
|
||||
check(1, true);
|
||||
RequestsMenu.focusFirstVisibleItem();
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusNextItem();
|
||||
check(1, true);
|
||||
RequestsMenu.focusPrevItem();
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusItemAtDelta(+1);
|
||||
check(1, true);
|
||||
RequestsMenu.focusItemAtDelta(-1);
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusItemAtDelta(+10);
|
||||
check(1, true);
|
||||
RequestsMenu.focusItemAtDelta(-10);
|
||||
check(0, true);
|
||||
|
||||
aDebuggee.performRequests(18);
|
||||
return waitForNetworkEvents(aMonitor, 18);
|
||||
})
|
||||
.then(() => {
|
||||
RequestsMenu.focusLastVisibleItem();
|
||||
check(19, true);
|
||||
RequestsMenu.focusFirstVisibleItem();
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusNextItem();
|
||||
check(1, true);
|
||||
RequestsMenu.focusPrevItem();
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusItemAtDelta(+10);
|
||||
check(10, true);
|
||||
RequestsMenu.focusItemAtDelta(-10);
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusItemAtDelta(+100);
|
||||
check(19, true);
|
||||
RequestsMenu.focusItemAtDelta(-100);
|
||||
check(0, true);
|
||||
|
||||
teardown(aMonitor).then(finish);
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
|
||||
function check(aSelectedIndex, aPaneVisibility) {
|
||||
info("Performing check " + (count++) + ".");
|
||||
|
||||
is(RequestsMenu.selectedIndex, aSelectedIndex,
|
||||
"The selected item in the requests menu was incorrect.");
|
||||
is(NetMonitorView.detailsPaneHidden, !aPaneVisibility,
|
||||
"The network requests details pane visibility state was incorrect.");
|
||||
}
|
||||
|
||||
aDebuggee.performRequests(2);
|
||||
});
|
||||
}
|
123
browser/devtools/netmonitor/test/browser_net_accessibility-02.js
Normal file
123
browser/devtools/netmonitor/test/browser_net_accessibility-02.js
Normal file
@ -0,0 +1,123 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if keyboard and mouse navigation works in the network requests menu.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
initNetMonitor(CUSTOM_GET_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
let { window, $, NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 2).then(() => {
|
||||
check(-1, false);
|
||||
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("END", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
|
||||
aDebuggee.performRequests(18);
|
||||
return waitForNetworkEvents(aMonitor, 18);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(2, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(4, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(8, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(4, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("END", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("END", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("END", window);
|
||||
check(19, true);
|
||||
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(15, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(11, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(10, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(9, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(13, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(17, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(19, true);
|
||||
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("END", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(19, true);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
check(-1, false);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $(".side-menu-widget-item"));
|
||||
check(0, true);
|
||||
|
||||
teardown(aMonitor).then(finish);
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
|
||||
function check(aSelectedIndex, aPaneVisibility) {
|
||||
info("Performing check " + (count++) + ".");
|
||||
|
||||
is(RequestsMenu.selectedIndex, aSelectedIndex,
|
||||
"The selected item in the requests menu was incorrect.");
|
||||
is(NetMonitorView.detailsPaneHidden, !aPaneVisibility,
|
||||
"The network requests details pane visibility state was incorrect.");
|
||||
}
|
||||
|
||||
aDebuggee.performRequests(2);
|
||||
});
|
||||
}
|
@ -21,7 +21,8 @@ function test() {
|
||||
// when the requests overflow the vertical size of the container.
|
||||
.then(() => {
|
||||
return waitForRequestsToOverflowContainer(monitor, requestsContainer);
|
||||
}).then(() => {
|
||||
})
|
||||
.then(() => {
|
||||
ok(scrolledToBottom(requestsContainer), "Scrolled to bottom on overflow.");
|
||||
})
|
||||
|
||||
@ -34,7 +35,8 @@ function test() {
|
||||
ok(!scrolledToBottom(requestsContainer), "Not scrolled to bottom.");
|
||||
scrollTop = requestsContainer.scrollTop; // save for comparison later
|
||||
return waitForNetworkEvents(monitor, 8);
|
||||
}).then(() => {
|
||||
})
|
||||
.then(() => {
|
||||
is(requestsContainer.scrollTop, scrollTop, "Did not scroll.");
|
||||
})
|
||||
|
||||
@ -44,10 +46,21 @@ function test() {
|
||||
requestsContainer.scrollTop = requestsContainer.scrollHeight;
|
||||
ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom.");
|
||||
return waitForNetworkEvents(monitor, 8);
|
||||
}).then(() => {
|
||||
})
|
||||
.then(() => {
|
||||
ok(scrolledToBottom(requestsContainer), "Still scrolled to bottom.");
|
||||
})
|
||||
|
||||
// (4) Now select an item in the list and check that additional requests
|
||||
// do not change the scroll position.
|
||||
.then(() => {
|
||||
monitor.panelWin.NetMonitorView.RequestsMenu.selectedIndex = 0;
|
||||
return waitForNetworkEvents(monitor, 8);
|
||||
})
|
||||
.then(() => {
|
||||
is(requestsContainer.scrollTop, 0, "Did not scroll.");
|
||||
})
|
||||
|
||||
// Done; clean up.
|
||||
.then(() => {
|
||||
return teardown(monitor).then(finish);
|
||||
|
@ -9,7 +9,7 @@ function test() {
|
||||
initNetMonitor(CYRILLIC_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, L10N, SourceEditor, NetMonitorView } = aMonitor.panelWin;
|
||||
let { document, SourceEditor, NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
42
browser/devtools/netmonitor/test/browser_net_cyrillic-02.js
Normal file
42
browser/devtools/netmonitor/test/browser_net_cyrillic-02.js
Normal file
@ -0,0 +1,42 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if cyrillic text is rendered correctly in the source editor
|
||||
* when loaded directly from an HTML page.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
initNetMonitor(CYRILLIC_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, SourceEditor, NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 1).then(() => {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CYRILLIC_URL, {
|
||||
status: 200,
|
||||
statusText: "OK"
|
||||
});
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
|
||||
NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
|
||||
is(aEditor.getText().indexOf("\u044F"), 189, // я
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(aEditor.getMode(), SourceEditor.MODES.HTML,
|
||||
"The mode active in the source editor is incorrect.");
|
||||
|
||||
teardown(aMonitor).then(finish);
|
||||
});
|
||||
});
|
||||
|
||||
aDebuggee.location.reload();
|
||||
});
|
||||
}
|
184
browser/devtools/netmonitor/test/browser_net_filter-01.js
Normal file
184
browser/devtools/netmonitor/test/browser_net_filter-01.js
Normal file
@ -0,0 +1,184 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test if filtering items in the network table works correctly.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 8).then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should be a selected item in the requests menu.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be selected in the requests menu.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
|
||||
testButtons();
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1])
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testButtons("html");
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
testButtons("css");
|
||||
return testContents([0, 1, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
|
||||
testButtons("js");
|
||||
return testContents([0, 0, 1, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-xhr-button"));
|
||||
testButtons("xhr");
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-fonts-button"));
|
||||
testButtons("fonts");
|
||||
return testContents([0, 0, 0, 1, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-images-button"));
|
||||
testButtons("images");
|
||||
return testContents([0, 0, 0, 0, 1, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-media-button"));
|
||||
testButtons("media");
|
||||
return testContents([0, 0, 0, 0, 0, 1, 1, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
|
||||
testButtons("flash");
|
||||
return testContents([0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
testButtons();
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
})
|
||||
.then(() => {
|
||||
return teardown(aMonitor);
|
||||
})
|
||||
.then(finish);
|
||||
});
|
||||
|
||||
function testButtons(aFilterType) {
|
||||
let doc = aMonitor.panelWin.document;
|
||||
let target = doc.querySelector("#requests-menu-filter-" + aFilterType + "-button");
|
||||
let buttons = doc.querySelectorAll(".requests-menu-footer-button");
|
||||
|
||||
for (let button of buttons) {
|
||||
if (button != target) {
|
||||
is(button.hasAttribute("checked"), false,
|
||||
"The " + button.id + " button should not have a 'checked' attribute.");
|
||||
} else {
|
||||
is(button.hasAttribute("checked"), true,
|
||||
"The " + button.id + " button should have a 'checked' attribute.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testContents(aVisibility) {
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should still be a selected item after filtering.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be still selected after filtering.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should still be visible after filtering.");
|
||||
|
||||
is(RequestsMenu.orderedItems.length, aVisibility.length,
|
||||
"There should be a specific amount of items in the requests menu.");
|
||||
is(RequestsMenu.visibleItems.length, aVisibility.filter(e => e).length,
|
||||
"There should be a specific amount of visbile items in the requests menu.");
|
||||
|
||||
for (let i = 0; i < aVisibility.length; i++) {
|
||||
is(RequestsMenu.getItemAtIndex(i).target.hidden, !aVisibility[i],
|
||||
"The item at index " + i + " doesn't have the correct hidden state.");
|
||||
}
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=html", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "css",
|
||||
fullMimeType: "text/css; charset=utf-8"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "js",
|
||||
fullMimeType: "application/javascript; charset=utf-8"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=font", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "woff",
|
||||
fullMimeType: "font/woff"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=image", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "png",
|
||||
fullMimeType: "image/png"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=audio", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "ogg",
|
||||
fullMimeType: "audio/ogg"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=video", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "webm",
|
||||
fullMimeType: "video/webm"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(7),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=flash", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "x-shockwave-flash",
|
||||
fullMimeType: "application/x-shockwave-flash"
|
||||
});
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
aDebuggee.performRequests('{ "getMedia": true, "getFlash": true }');
|
||||
});
|
||||
}
|
181
browser/devtools/netmonitor/test/browser_net_filter-02.js
Normal file
181
browser/devtools/netmonitor/test/browser_net_filter-02.js
Normal file
@ -0,0 +1,181 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test if filtering items in the network table works correctly with new requests.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 8).then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should be a selected item in the requests menu.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be selected in the requests menu.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
|
||||
testButtons();
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1])
|
||||
.then(() => {
|
||||
info("Testing html filtering.");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testButtons("html");
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
info("Performing more requests.");
|
||||
aDebuggee.performRequests('{ "getMedia": true, "getFlash": true }');
|
||||
return waitForNetworkEvents(aMonitor, 8);
|
||||
})
|
||||
.then(() => {
|
||||
info("Testing html filtering again.");
|
||||
testButtons("html");
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
info("Performing more requests.");
|
||||
aDebuggee.performRequests('{ "getMedia": true, "getFlash": true }');
|
||||
return waitForNetworkEvents(aMonitor, 8);
|
||||
})
|
||||
.then(() => {
|
||||
info("Testing html filtering again.");
|
||||
testButtons("html");
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
return teardown(aMonitor);
|
||||
})
|
||||
.then(finish);
|
||||
});
|
||||
|
||||
function testButtons(aFilterType) {
|
||||
let doc = aMonitor.panelWin.document;
|
||||
let target = doc.querySelector("#requests-menu-filter-" + aFilterType + "-button");
|
||||
let buttons = doc.querySelectorAll(".requests-menu-footer-button");
|
||||
|
||||
for (let button of buttons) {
|
||||
if (button != target) {
|
||||
is(button.hasAttribute("checked"), false,
|
||||
"The " + button.id + " button should not have a 'checked' attribute.");
|
||||
} else {
|
||||
is(button.hasAttribute("checked"), true,
|
||||
"The " + button.id + " button should have a 'checked' attribute.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testContents(aVisibility) {
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should still be a selected item after filtering.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be still selected after filtering.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should still be visible after filtering.");
|
||||
|
||||
is(RequestsMenu.orderedItems.length, aVisibility.length,
|
||||
"There should be a specific amount of items in the requests menu.");
|
||||
is(RequestsMenu.visibleItems.length, aVisibility.filter(e => e).length,
|
||||
"There should be a specific amount of visbile items in the requests menu.");
|
||||
|
||||
for (let i = 0; i < aVisibility.length; i++) {
|
||||
is(RequestsMenu.getItemAtIndex(i).target.hidden, !aVisibility[i],
|
||||
"The item at index " + i + " doesn't have the correct hidden state.");
|
||||
}
|
||||
|
||||
for (let i = 0; i < aVisibility.length; i += 8) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=html", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 1; i < aVisibility.length; i += 8) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "css",
|
||||
fullMimeType: "text/css; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 2; i < aVisibility.length; i += 8) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "js",
|
||||
fullMimeType: "application/javascript; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 3; i < aVisibility.length; i += 8) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=font", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "woff",
|
||||
fullMimeType: "font/woff"
|
||||
});
|
||||
}
|
||||
for (let i = 4; i < aVisibility.length; i += 8) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=image", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "png",
|
||||
fullMimeType: "image/png"
|
||||
});
|
||||
}
|
||||
for (let i = 5; i < aVisibility.length; i += 8) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=audio", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "ogg",
|
||||
fullMimeType: "audio/ogg"
|
||||
});
|
||||
}
|
||||
for (let i = 6; i < aVisibility.length; i += 8) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=video", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "webm",
|
||||
fullMimeType: "video/webm"
|
||||
});
|
||||
}
|
||||
for (let i = 7; i < aVisibility.length; i += 8) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=flash", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "x-shockwave-flash",
|
||||
fullMimeType: "application/x-shockwave-flash"
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
aDebuggee.performRequests('{ "getMedia": true, "getFlash": true }');
|
||||
});
|
||||
}
|
187
browser/devtools/netmonitor/test/browser_net_filter-03.js
Normal file
187
browser/devtools/netmonitor/test/browser_net_filter-03.js
Normal file
@ -0,0 +1,187 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test if filtering items in the network table works correctly with new requests
|
||||
* and while sorting is enabled.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 7).then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should be a selected item in the requests menu.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be selected in the requests menu.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
|
||||
testButtons();
|
||||
testContents([0, 1, 2, 3, 4, 5, 6], 7, 0)
|
||||
.then(() => {
|
||||
info("Sorting by size, ascending.");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
|
||||
testButtons();
|
||||
return testContents([6, 4, 5, 0, 1, 2, 3], 7, 6);
|
||||
})
|
||||
.then(() => {
|
||||
info("Testing html filtering.");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testButtons("html");
|
||||
return testContents([6, 4, 5, 0, 1, 2, 3], 1, 6);
|
||||
})
|
||||
.then(() => {
|
||||
info("Performing more requests.");
|
||||
aDebuggee.performRequests('{ "getMedia": true }');
|
||||
return waitForNetworkEvents(aMonitor, 7);
|
||||
})
|
||||
.then(() => {
|
||||
info("Testing html filtering again.");
|
||||
resetSorting();
|
||||
testButtons("html");
|
||||
return testContents([8, 13, 9, 11, 10, 12, 0, 4, 1, 5, 2, 6, 3, 7], 2, 13);
|
||||
})
|
||||
.then(() => {
|
||||
info("Performing more requests.");
|
||||
aDebuggee.performRequests('{ "getMedia": true }');
|
||||
return waitForNetworkEvents(aMonitor, 7);
|
||||
})
|
||||
.then(() => {
|
||||
info("Testing html filtering again.");
|
||||
resetSorting();
|
||||
testButtons("html");
|
||||
return testContents([12, 13, 20, 14, 16, 18, 15, 17, 19, 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11], 3, 20);
|
||||
})
|
||||
.then(() => {
|
||||
return teardown(aMonitor);
|
||||
})
|
||||
.then(finish);
|
||||
});
|
||||
|
||||
function resetSorting() {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
|
||||
}
|
||||
}
|
||||
|
||||
function testButtons(aFilterType) {
|
||||
let doc = aMonitor.panelWin.document;
|
||||
let target = doc.querySelector("#requests-menu-filter-" + aFilterType + "-button");
|
||||
let buttons = doc.querySelectorAll(".requests-menu-footer-button");
|
||||
|
||||
for (let button of buttons) {
|
||||
if (button != target) {
|
||||
is(button.hasAttribute("checked"), false,
|
||||
"The " + button.id + " button should not have a 'checked' attribute.");
|
||||
} else {
|
||||
is(button.hasAttribute("checked"), true,
|
||||
"The " + button.id + " button should have a 'checked' attribute.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testContents(aOrder, aVisible, aSelection) {
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should still be a selected item after filtering.");
|
||||
is(RequestsMenu.selectedIndex, aSelection,
|
||||
"The first item should be still selected after filtering.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should still be visible after filtering.");
|
||||
|
||||
is(RequestsMenu.orderedItems.length, aOrder.length,
|
||||
"There should be a specific amount of items in the requests menu.");
|
||||
is(RequestsMenu.visibleItems.length, aVisible,
|
||||
"There should be a specific amount of visbile items in the requests menu.");
|
||||
|
||||
for (let i = 0; i < aOrder.length; i++) {
|
||||
is(RequestsMenu.getItemAtIndex(i), RequestsMenu.orderedItems[i],
|
||||
"The requests menu items aren't ordered correctly. Misplaced item " + i + ".");
|
||||
}
|
||||
|
||||
for (let i = 0, len = aOrder.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=html", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = aOrder.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "css",
|
||||
fullMimeType: "text/css; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = aOrder.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 2]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "js",
|
||||
fullMimeType: "application/javascript; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = aOrder.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 3]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=font", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "woff",
|
||||
fullMimeType: "font/woff"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = aOrder.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 4]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=image", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "png",
|
||||
fullMimeType: "image/png"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = aOrder.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 5]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=audio", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "ogg",
|
||||
fullMimeType: "audio/ogg"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = aOrder.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 6]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=video", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "webm",
|
||||
fullMimeType: "video/webm"
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
let str = "'<p>'" + new Array(10).join(Math.random(10)) + "'</p>'";
|
||||
aDebuggee.performRequests('{ "htmlContent": "' + str + '", "getMedia": true }');
|
||||
});
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if very large response contents are just displayed as plain text.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
initNetMonitor(CUSTOM_GET_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
// This test could potentially be slow because over 100 KB of stuff
|
||||
// is going to be requested and displayed in the source editor.
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let { document, SourceEditor, NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 1).then(() => {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=html-long", {
|
||||
status: 200,
|
||||
statusText: "OK"
|
||||
});
|
||||
|
||||
aMonitor.panelWin.once("NetMonitor:ResponseBodyAvailable", () => {
|
||||
NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
|
||||
ok(aEditor.getText().match(/^<p>/),
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(aEditor.getMode(), SourceEditor.MODES.TEXT,
|
||||
"The mode active in the source editor is incorrect.");
|
||||
|
||||
teardown(aMonitor).then(finish);
|
||||
});
|
||||
});
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
});
|
||||
|
||||
aDebuggee.performRequests(1, CONTENT_TYPE_SJS + "?fmt=html-long");
|
||||
});
|
||||
}
|
79
browser/devtools/netmonitor/test/browser_net_pane-toggle.js
Normal file
79
browser/devtools/netmonitor/test/browser_net_pane-toggle.js
Normal file
@ -0,0 +1,79 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if toggling the details pane works as expected.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("disabled"), true,
|
||||
"The pane toggle button should be disabled when the frontend is opened.");
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("pane-collapsed"), true,
|
||||
"The pane toggle button should indicate that the details pane is " +
|
||||
"collapsed when the frontend is opened.");
|
||||
is(NetMonitorView.detailsPaneHidden, true,
|
||||
"The details pane should be hidden when the frontend is opened.");
|
||||
is(RequestsMenu.selectedItem, null,
|
||||
"There should be no selected item in the requests menu.");
|
||||
|
||||
aMonitor.panelWin.once("NetMonitor:NetworkEvent", () => {
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("disabled"), false,
|
||||
"The pane toggle button should be enabled after the first request.");
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("pane-collapsed"), true,
|
||||
"The pane toggle button should still indicate that the details pane is " +
|
||||
"collapsed after the first request.");
|
||||
is(NetMonitorView.detailsPaneHidden, true,
|
||||
"The details pane should still be hidden after the first request.");
|
||||
is(RequestsMenu.selectedItem, null,
|
||||
"There should still be no selected item in the requests menu.");
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("disabled"), false,
|
||||
"The pane toggle button should still be enabled after being pressed.");
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("pane-collapsed"), false,
|
||||
"The pane toggle button should now indicate that the details pane is " +
|
||||
"not collapsed anymore after being pressed.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should be a selected item in the requests menu.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be selected in the requests menu.");
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("disabled"), false,
|
||||
"The pane toggle button should still be enabled after being pressed again.");
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("pane-collapsed"), true,
|
||||
"The pane toggle button should now indicate that the details pane is " +
|
||||
"collapsed after being pressed again.");
|
||||
is(NetMonitorView.detailsPaneHidden, true,
|
||||
"The details pane should now be hidden after the toggle button was pressed again.");
|
||||
is(RequestsMenu.selectedItem, null,
|
||||
"There should now be no selected item in the requests menu.");
|
||||
|
||||
teardown(aMonitor).then(finish);
|
||||
});
|
||||
|
||||
aDebuggee.location.reload();
|
||||
});
|
||||
}
|
@ -17,7 +17,7 @@ function test() {
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("disabled"), true,
|
||||
"The pane toggle button should be disabled when the frontend is opened.");
|
||||
is(document.querySelector(".requests-menu-empty-notice")
|
||||
is(document.querySelector("#requests-menu-empty-notice")
|
||||
.hasAttribute("hidden"), false,
|
||||
"An empty notice should be displayed when the frontend is opened.");
|
||||
is(RequestsMenu.itemCount, 0,
|
||||
@ -29,7 +29,7 @@ function test() {
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("disabled"), false,
|
||||
"The pane toggle button should be enabled after the first request.");
|
||||
is(document.querySelector(".requests-menu-empty-notice")
|
||||
is(document.querySelector("#requests-menu-empty-notice")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The empty notice should be hidden after the first request.");
|
||||
is(RequestsMenu.itemCount, 1,
|
||||
@ -41,7 +41,7 @@ function test() {
|
||||
is(document.querySelector("#details-pane-toggle")
|
||||
.hasAttribute("disabled"), false,
|
||||
"The pane toggle button should be still be enabled after a reload.");
|
||||
is(document.querySelector(".requests-menu-empty-notice")
|
||||
is(document.querySelector("#requests-menu-empty-notice")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The empty notice should be still hidden after a reload.");
|
||||
is(RequestsMenu.itemCount, 1,
|
||||
|
@ -142,6 +142,7 @@ function test() {
|
||||
return testContents([3, 4, 0, 1, 2]);
|
||||
})
|
||||
.then(() => {
|
||||
info("Clearing sort.");
|
||||
RequestsMenu.sortBy(null);
|
||||
return testContents([0, 1, 2, 3, 4]);
|
||||
})
|
||||
@ -152,22 +153,20 @@ function test() {
|
||||
});
|
||||
|
||||
function testContents([a, b, c, d, e]) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
is(RequestsMenu.allItems.length, 5,
|
||||
is(RequestsMenu.orderedItems.length, 5,
|
||||
"There should be a total of 5 items in the requests menu.");
|
||||
is(RequestsMenu.visibleItems.length, 5,
|
||||
"There should be a total of 5 visbile items in the requests menu.");
|
||||
|
||||
is(RequestsMenu.getItemAtIndex(0), RequestsMenu.allItems[0],
|
||||
is(RequestsMenu.getItemAtIndex(0), RequestsMenu.orderedItems[0],
|
||||
"The requests menu items aren't ordered correctly. First item is misplaced.");
|
||||
is(RequestsMenu.getItemAtIndex(1), RequestsMenu.allItems[1],
|
||||
is(RequestsMenu.getItemAtIndex(1), RequestsMenu.orderedItems[1],
|
||||
"The requests menu items aren't ordered correctly. Second item is misplaced.");
|
||||
is(RequestsMenu.getItemAtIndex(2), RequestsMenu.allItems[2],
|
||||
is(RequestsMenu.getItemAtIndex(2), RequestsMenu.orderedItems[2],
|
||||
"The requests menu items aren't ordered correctly. Third item is misplaced.");
|
||||
is(RequestsMenu.getItemAtIndex(3), RequestsMenu.allItems[3],
|
||||
is(RequestsMenu.getItemAtIndex(3), RequestsMenu.orderedItems[3],
|
||||
"The requests menu items aren't ordered correctly. Fourth item is misplaced.");
|
||||
is(RequestsMenu.getItemAtIndex(4), RequestsMenu.allItems[4],
|
||||
is(RequestsMenu.getItemAtIndex(4), RequestsMenu.orderedItems[4],
|
||||
"The requests menu items aren't ordered correctly. Fifth item is misplaced.");
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
|
||||
@ -216,8 +215,7 @@ function test() {
|
||||
time: true
|
||||
});
|
||||
|
||||
executeSoon(deferred.resolve);
|
||||
return deferred.promise;
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
aDebuggee.performRequests();
|
||||
|
@ -149,8 +149,6 @@ function test() {
|
||||
}
|
||||
|
||||
function testContents([a, b, c, d, e]) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should still be a selected item after sorting.");
|
||||
is(RequestsMenu.selectedIndex, a,
|
||||
@ -158,20 +156,20 @@ function test() {
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should still be visible after sorting.");
|
||||
|
||||
is(RequestsMenu.allItems.length, 5,
|
||||
is(RequestsMenu.orderedItems.length, 5,
|
||||
"There should be a total of 5 items in the requests menu.");
|
||||
is(RequestsMenu.visibleItems.length, 5,
|
||||
"There should be a total of 5 visbile items in the requests menu.");
|
||||
|
||||
is(RequestsMenu.getItemAtIndex(0), RequestsMenu.allItems[0],
|
||||
is(RequestsMenu.getItemAtIndex(0), RequestsMenu.orderedItems[0],
|
||||
"The requests menu items aren't ordered correctly. First item is misplaced.");
|
||||
is(RequestsMenu.getItemAtIndex(1), RequestsMenu.allItems[1],
|
||||
is(RequestsMenu.getItemAtIndex(1), RequestsMenu.orderedItems[1],
|
||||
"The requests menu items aren't ordered correctly. Second item is misplaced.");
|
||||
is(RequestsMenu.getItemAtIndex(2), RequestsMenu.allItems[2],
|
||||
is(RequestsMenu.getItemAtIndex(2), RequestsMenu.orderedItems[2],
|
||||
"The requests menu items aren't ordered correctly. Third item is misplaced.");
|
||||
is(RequestsMenu.getItemAtIndex(3), RequestsMenu.allItems[3],
|
||||
is(RequestsMenu.getItemAtIndex(3), RequestsMenu.orderedItems[3],
|
||||
"The requests menu items aren't ordered correctly. Fourth item is misplaced.");
|
||||
is(RequestsMenu.getItemAtIndex(4), RequestsMenu.allItems[4],
|
||||
is(RequestsMenu.getItemAtIndex(4), RequestsMenu.orderedItems[4],
|
||||
"The requests menu items aren't ordered correctly. Fifth item is misplaced.");
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
|
||||
@ -225,8 +223,7 @@ function test() {
|
||||
time: true
|
||||
});
|
||||
|
||||
executeSoon(deferred.resolve);
|
||||
return deferred.promise;
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
aDebuggee.performRequests();
|
||||
|
@ -91,8 +91,6 @@ function test() {
|
||||
}
|
||||
|
||||
function testContents(aOrder, aSelection) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should still be a selected item after sorting.");
|
||||
is(RequestsMenu.selectedIndex, aSelection,
|
||||
@ -100,13 +98,13 @@ function test() {
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should still be visible after sorting.");
|
||||
|
||||
is(RequestsMenu.allItems.length, aOrder.length,
|
||||
is(RequestsMenu.orderedItems.length, aOrder.length,
|
||||
"There should be a specific number of items in the requests menu.");
|
||||
is(RequestsMenu.visibleItems.length, aOrder.length,
|
||||
"There should be a specific number of visbile items in the requests menu.");
|
||||
|
||||
for (let i = 0; i < aOrder.length; i++) {
|
||||
is(RequestsMenu.getItemAtIndex(i), RequestsMenu.allItems[i],
|
||||
is(RequestsMenu.getItemAtIndex(i), RequestsMenu.orderedItems[i],
|
||||
"The requests menu items aren't ordered correctly. Misplaced item " + i + ".");
|
||||
}
|
||||
|
||||
@ -171,8 +169,7 @@ function test() {
|
||||
});
|
||||
}
|
||||
|
||||
executeSoon(deferred.resolve);
|
||||
return deferred.promise;
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
aDebuggee.performRequests();
|
||||
|
@ -14,8 +14,7 @@ function test() {
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
is(document.querySelector(".requests-menu-empty-notice")
|
||||
.hasAttribute("hidden"), false,
|
||||
ok(document.querySelector("#requests-menu-waterfall-label"),
|
||||
"An timeline label should be displayed when the frontend is opened.");
|
||||
ok(document.querySelectorAll(".requests-menu-timings-division").length == 0,
|
||||
"No tick labels should be displayed when the frontend is opened.");
|
||||
@ -26,8 +25,7 @@ function test() {
|
||||
"No 2d context should be created when the frontend is opened.");
|
||||
|
||||
waitForNetworkEvents(aMonitor, 1).then(() => {
|
||||
is(document.querySelector(".requests-menu-empty-notice")
|
||||
.hasAttribute("hidden"), true,
|
||||
ok(!document.querySelector("#requests-menu-waterfall-label"),
|
||||
"The timeline label should be hidden after the first request.");
|
||||
ok(document.querySelectorAll(".requests-menu-timings-division").length >= 3,
|
||||
"There should be at least 3 tick labels in the network requests header.");
|
||||
|
@ -22,7 +22,9 @@ const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
|
||||
const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
|
||||
const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
|
||||
const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
|
||||
const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
|
||||
const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
|
||||
const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-page.html";
|
||||
|
||||
const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
|
||||
const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
|
||||
|
39
browser/devtools/netmonitor/test/html_custom-get-page.html
Normal file
39
browser/devtools/netmonitor/test/html_custom-get-page.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Network Monitor test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Performing a custom number of GETs</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
function get(aAddress, aCallback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", aAddress, true);
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState == this.DONE) {
|
||||
aCallback();
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
// Use a count parameter to defeat caching.
|
||||
var count = 0;
|
||||
|
||||
function performRequests(aTotal, aUrl, aTimeout = 0) {
|
||||
if (!aTotal) {
|
||||
return;
|
||||
}
|
||||
get(aUrl || "request_" + (count++), function() {
|
||||
setTimeout(performRequests.bind(this, --aTotal, aUrl, aTimeout), aTimeout);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -8,6 +8,7 @@
|
||||
|
||||
<body>
|
||||
<p>Cyrillic type test</p>
|
||||
<p>Братан, ты вообще качаешься?</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
function get(aAddress, aCallback) {
|
||||
|
55
browser/devtools/netmonitor/test/html_filter-test-page.html
Normal file
55
browser/devtools/netmonitor/test/html_filter-test-page.html
Normal file
@ -0,0 +1,55 @@
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Network Monitor test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Filtering test</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
function get(aAddress, aCallback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
// Use a random parameter to defeat caching.
|
||||
xhr.open("GET", aAddress + "&" + Math.random(), true);
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState == this.DONE) {
|
||||
aCallback();
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
function performRequests(aOptions) {
|
||||
var options = JSON.parse(aOptions);
|
||||
get("sjs_content-type-test-server.sjs?fmt=html&res=" + options.htmlContent, function() {
|
||||
get("sjs_content-type-test-server.sjs?fmt=css", function() {
|
||||
get("sjs_content-type-test-server.sjs?fmt=js", function() {
|
||||
if (!options.getMedia) {
|
||||
return;
|
||||
}
|
||||
get("sjs_content-type-test-server.sjs?fmt=font", function() {
|
||||
get("sjs_content-type-test-server.sjs?fmt=image", function() {
|
||||
get("sjs_content-type-test-server.sjs?fmt=audio", function() {
|
||||
get("sjs_content-type-test-server.sjs?fmt=video", function() {
|
||||
if (!options.getFlash) {
|
||||
return;
|
||||
}
|
||||
get("sjs_content-type-test-server.sjs?fmt=flash", function() {
|
||||
// Done.
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -27,7 +27,7 @@
|
||||
|
||||
(function performRequests() {
|
||||
get("request_" + (count++), function() {
|
||||
setTimeout(performRequests, 0);
|
||||
setTimeout(performRequests, 50);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
@ -25,6 +25,22 @@ function handleRequest(request, response) {
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "html": {
|
||||
let content = params.filter((s) => s.contains("res="))[0].split("=")[1];
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
|
||||
response.write(content || "<p>Hello HTML!</p>");
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "html-long": {
|
||||
let str = new Array(102400 /* 100 KB in bytes */).join(".");
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
|
||||
response.write("<p>" + str + "</p>");
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "css": {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/css; charset=utf-8", false);
|
||||
@ -62,6 +78,36 @@ function handleRequest(request, response) {
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "font": {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "font/woff", false);
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "image": {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "image/png", false);
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "audio": {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "audio/ogg", false);
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "video": {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "video/webm", false);
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "flash": {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "application/x-shockwave-flash", false);
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
response.setStatusLine(request.httpVersion, 404, "Not Found");
|
||||
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
|
||||
|
@ -13,6 +13,9 @@ Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
Cu.import("resource:///modules/devtools/FloatingScrollbars.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
|
||||
var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
|
||||
|
||||
const MIN_WIDTH = 50;
|
||||
@ -108,6 +111,7 @@ function ResponsiveUI(aWindow, aTab)
|
||||
this.chromeDoc = aWindow.document;
|
||||
this.container = aWindow.gBrowser.getBrowserContainer(this.browser);
|
||||
this.stack = this.container.querySelector(".browserStack");
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
// Try to load presets from prefs
|
||||
if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
|
||||
@ -178,6 +182,8 @@ function ResponsiveUI(aWindow, aTab)
|
||||
|
||||
this.tab.__responsiveUI = this;
|
||||
|
||||
this._telemetry.toolOpened("responsive");
|
||||
|
||||
ResponsiveUIManager.emit("on", this.tab, this);
|
||||
}
|
||||
|
||||
@ -236,6 +242,7 @@ ResponsiveUI.prototype = {
|
||||
this.stack.removeAttribute("responsivemode");
|
||||
|
||||
delete this.tab.__responsiveUI;
|
||||
this._telemetry.toolClosed("responsive");
|
||||
ResponsiveUIManager.emit("off", this.tab, this);
|
||||
},
|
||||
|
||||
|
@ -35,6 +35,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
|
||||
"resource://gre/modules/devtools/Loader.jsm");
|
||||
|
||||
let Telemetry = devtools.require("devtools/shared/telemetry");
|
||||
|
||||
const SCRATCHPAD_CONTEXT_CONTENT = 1;
|
||||
const SCRATCHPAD_CONTEXT_BROWSER = 2;
|
||||
const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
|
||||
@ -46,6 +48,10 @@ const BUTTON_POSITION_DONT_SAVE = 2;
|
||||
const BUTTON_POSITION_REVERT = 0;
|
||||
const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
|
||||
|
||||
// Because we have no constructor / destructor where we can log metrics we need
|
||||
// to do so here.
|
||||
let telemetry = new Telemetry();
|
||||
telemetry.toolOpened("scratchpad");
|
||||
|
||||
/**
|
||||
* The scratchpad object handles the Scratchpad window functionality.
|
||||
@ -1390,6 +1396,7 @@ var Scratchpad = {
|
||||
}
|
||||
|
||||
if (shouldClose) {
|
||||
telemetry.toolClosed("scratchpad");
|
||||
window.close();
|
||||
}
|
||||
if (aCallback) {
|
||||
|
@ -8,12 +8,13 @@ this.EXPORTED_SYMBOLS = [ "DeveloperToolbar", "CommandUtils" ];
|
||||
|
||||
const NS_XHTML = "http://www.w3.org/1999/xhtml";
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource:///modules/devtools/Commands.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/Commands.jsm");
|
||||
|
||||
const Node = Components.interfaces.nsIDOMNode;
|
||||
const Node = Ci.nsIDOMNode;
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
@ -40,16 +41,18 @@ XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
|
||||
"resource:///modules/devtools/shared/event-emitter.js");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "prefBranch", function() {
|
||||
let prefService = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefService);
|
||||
let prefService = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService);
|
||||
return prefService.getBranch(null)
|
||||
.QueryInterface(Components.interfaces.nsIPrefBranch2);
|
||||
.QueryInterface(Ci.nsIPrefBranch2);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "toolboxStrings", function () {
|
||||
return Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
|
||||
});
|
||||
|
||||
let Telemetry = devtools.require("devtools/shared/telemetry");
|
||||
|
||||
const converters = require("gcli/converters");
|
||||
|
||||
/**
|
||||
@ -61,8 +64,7 @@ let CommandUtils = {
|
||||
* @param aPref The name of the preference to read
|
||||
*/
|
||||
getCommandbarSpec: function CU_getCommandbarSpec(aPref) {
|
||||
let value = prefBranch.getComplexValue(aPref,
|
||||
Components.interfaces.nsISupportsString).data;
|
||||
let value = prefBranch.getComplexValue(aPref, Ci.nsISupportsString).data;
|
||||
return JSON.parse(value);
|
||||
},
|
||||
|
||||
@ -188,8 +190,7 @@ XPCOMUtils.defineLazyGetter(this, "isLinux", function () {
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "OS", function () {
|
||||
let os = Components.classes["@mozilla.org/xre/app-info;1"]
|
||||
.getService(Components.interfaces.nsIXULRuntime).OS;
|
||||
let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
|
||||
return os;
|
||||
});
|
||||
|
||||
@ -207,6 +208,7 @@ this.DeveloperToolbar = function DeveloperToolbar(aChromeWindow, aToolbarElement
|
||||
this._element.hidden = true;
|
||||
this._doc = this._element.ownerDocument;
|
||||
|
||||
this._telemetry = new Telemetry();
|
||||
this._lastState = NOTIFICATIONS.HIDE;
|
||||
this._pendingShowCallback = undefined;
|
||||
this._pendingHide = false;
|
||||
@ -339,6 +341,8 @@ DeveloperToolbar.prototype.show = function DT_show(aFocus, aCallback)
|
||||
|
||||
Services.prefs.setBoolPref("devtools.toolbar.visible", true);
|
||||
|
||||
this._telemetry.toolOpened("developertoolbar");
|
||||
|
||||
this._notify(NOTIFICATIONS.LOAD);
|
||||
this._pendingShowCallback = aCallback;
|
||||
this._pendingHide = false;
|
||||
@ -500,6 +504,7 @@ DeveloperToolbar.prototype.hide = function DT_hide()
|
||||
this._doc.getElementById("Tools:DevToolbar").setAttribute("checked", "false");
|
||||
this.destroy();
|
||||
|
||||
this._telemetry.toolClosed("developertoolbar");
|
||||
this._notify(NOTIFICATIONS.HIDE);
|
||||
};
|
||||
|
||||
|
259
browser/devtools/shared/telemetry.js
Normal file
259
browser/devtools/shared/telemetry.js
Normal file
@ -0,0 +1,259 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Telemetry.
|
||||
*
|
||||
* To add metrics for a tool:
|
||||
*
|
||||
* 1. Create boolean, flag and exponential entries in
|
||||
* toolkit/components/telemetry/Histograms.json. Each type is optional but it
|
||||
* is best if all three can be included.
|
||||
*
|
||||
* 2. Add your chart entries to browser/devtools/shared/telemetry.js
|
||||
* (Telemetry.prototype._histograms):
|
||||
* mytoolname: {
|
||||
* histogram: "DEVTOOLS_MYTOOLNAME_OPENED_BOOLEAN",
|
||||
* userHistogram: "DEVTOOLS_MYTOOLNAME_OPENED_PER_USER_FLAG",
|
||||
* timerHistogram: "DEVTOOLS_MYTOOLNAME_TIME_ACTIVE_SECONDS"
|
||||
* },
|
||||
*
|
||||
* 3. Include this module at the top of your tool. Use:
|
||||
* let Telemetry = require("devtools/shared/telemetry")
|
||||
*
|
||||
* 4. Create a telemetry instance in your tool's constructor:
|
||||
* this._telemetry = new Telemetry();
|
||||
*
|
||||
* 5. When your tool is opened call:
|
||||
* this._telemetry.toolOpened("mytoolname");
|
||||
*
|
||||
* 6. When your tool is closed call:
|
||||
* this._telemetry.toolClosed("mytoolname");
|
||||
*
|
||||
* Note:
|
||||
* You can view telemetry stats for your local Firefox instance via
|
||||
* about:telemetry.
|
||||
*
|
||||
* You can view telemetry stats for large groups of Firefox users at
|
||||
* metrics.mozilla.com.
|
||||
*/
|
||||
|
||||
const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version";
|
||||
|
||||
this.Telemetry = function() {
|
||||
// Bind pretty much all functions so that callers do not need to.
|
||||
this.toolOpened = this.toolOpened.bind(this);
|
||||
this.toolClosed = this.toolClosed.bind(this);
|
||||
this.log = this.log.bind(this);
|
||||
this.logOncePerBrowserVersion = this.logOncePerBrowserVersion.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
|
||||
this._timers = new Map();
|
||||
};
|
||||
|
||||
module.exports = Telemetry;
|
||||
|
||||
let {Cc, Ci, Cu} = require("chrome");
|
||||
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
|
||||
Telemetry.prototype = {
|
||||
_histograms: {
|
||||
toolbox: {
|
||||
timerHistogram: "DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
options: {
|
||||
histogram: "DEVTOOLS_OPTIONS_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_OPTIONS_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_OPTIONS_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
webconsole: {
|
||||
histogram: "DEVTOOLS_WEBCONSOLE_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_WEBCONSOLE_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_WEBCONSOLE_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
browserconsole: {
|
||||
histogram: "DEVTOOLS_BROWSERCONSOLE_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_BROWSERCONSOLE_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_BROWSERCONSOLE_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
inspector: {
|
||||
histogram: "DEVTOOLS_INSPECTOR_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_INSPECTOR_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_INSPECTOR_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
ruleview: {
|
||||
histogram: "DEVTOOLS_RULEVIEW_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_RULEVIEW_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_RULEVIEW_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
computedview: {
|
||||
histogram: "DEVTOOLS_COMPUTEDVIEW_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_COMPUTEDVIEW_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
layoutview: {
|
||||
histogram: "DEVTOOLS_LAYOUTVIEW_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_LAYOUTVIEW_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
fontinspector: {
|
||||
histogram: "DEVTOOLS_FONTINSPECTOR_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_FONTINSPECTOR_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
jsdebugger: {
|
||||
histogram: "DEVTOOLS_JSDEBUGGER_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_JSDEBUGGER_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_JSDEBUGGER_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
jsbrowserdebugger: {
|
||||
histogram: "DEVTOOLS_JSBROWSERDEBUGGER_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_JSBROWSERDEBUGGER_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_JSBROWSERDEBUGGER_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
styleeditor: {
|
||||
histogram: "DEVTOOLS_STYLEEDITOR_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_STYLEEDITOR_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_STYLEEDITOR_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
jsprofiler: {
|
||||
histogram: "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
netmonitor: {
|
||||
histogram: "DEVTOOLS_NETMONITOR_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_NETMONITOR_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
tilt: {
|
||||
histogram: "DEVTOOLS_TILT_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_TILT_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_TILT_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
paintflashing: {
|
||||
histogram: "DEVTOOLS_PAINTFLASHING_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_PAINTFLASHING_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_PAINTFLASHING_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
scratchpad: {
|
||||
histogram: "DEVTOOLS_SCRATCHPAD_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_SCRATCHPAD_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_SCRATCHPAD_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
responsive: {
|
||||
histogram: "DEVTOOLS_RESPONSIVE_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_RESPONSIVE_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_RESPONSIVE_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
developertoolbar: {
|
||||
histogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS"
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add an entry to a histogram.
|
||||
*
|
||||
* @param {String} id
|
||||
* Used to look up the relevant histogram ID and log true to that
|
||||
* histogram.
|
||||
*/
|
||||
toolOpened: function(id) {
|
||||
let charts = this._histograms[id];
|
||||
|
||||
if (!charts) {
|
||||
dump('Warning: An attempt was made to open a tool with an id of "' + id +
|
||||
'", which is not listed in Telemetry._histograms. ' +
|
||||
"Location: telemetry.js/toolOpened()\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (charts.histogram) {
|
||||
this.log(charts.histogram, true);
|
||||
}
|
||||
if (charts.userHistogram) {
|
||||
this.logOncePerBrowserVersion(charts.userHistogram, true);
|
||||
}
|
||||
if (charts.timerHistogram) {
|
||||
this._timers.set(charts.timerHistogram, new Date());
|
||||
}
|
||||
},
|
||||
|
||||
toolClosed: function(id) {
|
||||
let charts = this._histograms[id];
|
||||
|
||||
if (!charts || !charts.timerHistogram) {
|
||||
return;
|
||||
}
|
||||
|
||||
let startTime = this._timers.get(charts.timerHistogram);
|
||||
|
||||
if (startTime) {
|
||||
let time = (new Date() - startTime) / 1000;
|
||||
this.log(charts.timerHistogram, time);
|
||||
this._timers.delete(charts.timerHistogram);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log a value to a histogram.
|
||||
*
|
||||
* @param {String} histogramId
|
||||
* Histogram in which the data is to be stored.
|
||||
* @param value
|
||||
* Value to store.
|
||||
*/
|
||||
log: function(histogramId, value) {
|
||||
if (histogramId) {
|
||||
let histogram;
|
||||
|
||||
try {
|
||||
let histogram = Services.telemetry.getHistogramById(histogramId);
|
||||
histogram.add(value);
|
||||
} catch(e) {
|
||||
dump("Warning: An attempt was made to write to the " + histogramId +
|
||||
" histogram, which is not defined in Histograms.json\n");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log info about usage once per browser version. This allows us to discover
|
||||
* how many individual users are using our tools for each browser version.
|
||||
*
|
||||
* @param {String} perUserHistogram
|
||||
* Histogram in which the data is to be stored.
|
||||
*/
|
||||
logOncePerBrowserVersion: function(perUserHistogram, value) {
|
||||
let currentVersion = appInfo.version;
|
||||
let latest = Services.prefs.getCharPref(TOOLS_OPENED_PREF);
|
||||
let latestObj = JSON.parse(latest);
|
||||
|
||||
let lastVersionHistogramUpdated = latestObj[perUserHistogram];
|
||||
|
||||
if (typeof lastVersionHistogramUpdated == "undefined" ||
|
||||
lastVersionHistogramUpdated !== currentVersion) {
|
||||
latestObj[perUserHistogram] = currentVersion;
|
||||
latest = JSON.stringify(latestObj);
|
||||
Services.prefs.setCharPref(TOOLS_OPENED_PREF, latest);
|
||||
this.log(perUserHistogram, value);
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
for (let [histogram, time] of this._timers) {
|
||||
time = (new Date() - time) / 1000;
|
||||
|
||||
this.log(histogram, time);
|
||||
this._timers.delete(histogram);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "appInfo", function() {
|
||||
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
|
||||
});
|
@ -12,6 +12,8 @@ relativesrcdir = @relativesrcdir@
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MOCHITEST_BROWSER_FILES = \
|
||||
browser_telemetry_toolboxtabs.js \
|
||||
browser_telemetry_buttonsandsidebar.js \
|
||||
browser_require_basic.js \
|
||||
browser_templater_basic.js \
|
||||
browser_toolbar_basic.js \
|
||||
|
@ -0,0 +1,179 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_buttonsandsidebar.js</p>";
|
||||
|
||||
// Because we need to gather stats for the period of time that a tool has been
|
||||
// opened we make use of setTimeout() to create tool active times.
|
||||
const TOOL_DELAY = 200;
|
||||
|
||||
let {Promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
|
||||
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
function init() {
|
||||
Telemetry.prototype.telemetryInfo = {};
|
||||
Telemetry.prototype._oldlog = Telemetry.prototype.log;
|
||||
Telemetry.prototype.log = function(histogramId, value) {
|
||||
if (histogramId) {
|
||||
if (!this.telemetryInfo[histogramId]) {
|
||||
this.telemetryInfo[histogramId] = [];
|
||||
}
|
||||
|
||||
this.telemetryInfo[histogramId].push(value);
|
||||
}
|
||||
}
|
||||
|
||||
testButtons();
|
||||
}
|
||||
|
||||
function testButtons() {
|
||||
info("Testing buttons");
|
||||
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
let container = toolbox.doc.getElementById("toolbox-buttons");
|
||||
let buttons = container.getElementsByTagName("toolbarbutton");
|
||||
|
||||
// Copy HTMLCollection to array.
|
||||
buttons = Array.prototype.slice.call(buttons);
|
||||
|
||||
(function testButton() {
|
||||
let button = buttons.pop();
|
||||
|
||||
if (button) {
|
||||
info("Clicking button " + button.id);
|
||||
button.click();
|
||||
delayedClicks(button, 3).then(function(button) {
|
||||
if (buttons.length == 0) {
|
||||
// Remove scratchpads
|
||||
let wins = Services.wm.getEnumerator("devtools:scratchpad");
|
||||
while (wins.hasMoreElements()) {
|
||||
let win = wins.getNext();
|
||||
info("Closing scratchpad window");
|
||||
win.close();
|
||||
}
|
||||
|
||||
testSidebar();
|
||||
} else {
|
||||
setTimeout(testButton, TOOL_DELAY);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
}).then(null, reportError);
|
||||
}
|
||||
|
||||
function delayedClicks(node, clicks) {
|
||||
let deferred = Promise.defer();
|
||||
let clicked = 0;
|
||||
|
||||
setTimeout(function delayedClick() {
|
||||
info("Clicking button " + node.id);
|
||||
node.click();
|
||||
clicked++;
|
||||
|
||||
if (clicked >= clicks) {
|
||||
deferred.resolve(node);
|
||||
} else {
|
||||
setTimeout(delayedClick, TOOL_DELAY);
|
||||
}
|
||||
}, TOOL_DELAY);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testSidebar() {
|
||||
info("Testing sidebar");
|
||||
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
let inspector = toolbox.getCurrentPanel();
|
||||
let sidebarTools = ["ruleview", "computedview", "fontinspector", "layoutview"];
|
||||
|
||||
// Concatenate the array with itself so that we can open each tool twice.
|
||||
sidebarTools.push.apply(sidebarTools, sidebarTools);
|
||||
|
||||
setTimeout(function selectSidebarTab() {
|
||||
let tool = sidebarTools.pop();
|
||||
if (tool) {
|
||||
inspector.sidebar.select(tool);
|
||||
setTimeout(function() {
|
||||
setTimeout(selectSidebarTab, TOOL_DELAY);
|
||||
}, TOOL_DELAY);
|
||||
} else {
|
||||
checkResults();
|
||||
}
|
||||
}, TOOL_DELAY);
|
||||
});
|
||||
}
|
||||
|
||||
function checkResults() {
|
||||
let result = Telemetry.prototype.telemetryInfo;
|
||||
|
||||
for (let [histId, value] of Iterator(result)) {
|
||||
if (histId.startsWith("DEVTOOLS_INSPECTOR_")) {
|
||||
// Inspector stats are tested in browser_telemetry_toolboxtabs.js so we
|
||||
// skip them here because we only open the inspector once for this test.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
|
||||
ok(value.length === 1 && value[0] === true,
|
||||
"Per user value " + histId + " has a single value of true");
|
||||
} else if (histId.endsWith("OPENED_BOOLEAN")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element === true;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries are === true");
|
||||
} else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element > 0;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries have time > 0");
|
||||
}
|
||||
}
|
||||
|
||||
finishUp();
|
||||
}
|
||||
|
||||
function reportError(error) {
|
||||
let stack = " " + error.stack.replace(/\n?.*?@/g, "\n JS frame :: ");
|
||||
|
||||
ok(false, "ERROR: " + error + " at " + error.fileName + ":" +
|
||||
error.lineNumber + "\n\nStack trace:" + stack);
|
||||
finishUp();
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
Telemetry.prototype.log = Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype.telemetryInfo;
|
||||
|
||||
TargetFactory = Services = Promise = require = null;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
waitForFocus(init, content);
|
||||
}, true);
|
||||
|
||||
content.location = TEST_URI;
|
||||
}
|
125
browser/devtools/shared/test/browser_telemetry_toolboxtabs.js
Normal file
125
browser/devtools/shared/test/browser_telemetry_toolboxtabs.js
Normal file
@ -0,0 +1,125 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs.js</p>";
|
||||
|
||||
// Because we need to gather stats for the period of time that a tool has been
|
||||
// opened we make use of setTimeout() to create tool active times.
|
||||
const TOOL_DELAY = 200;
|
||||
|
||||
let {Promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
|
||||
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
let tabsToTest = ["inspector", "options", "webconsole", "jsdebugger",
|
||||
"styleeditor", "jsprofiler", "netmonitor"];
|
||||
|
||||
function init() {
|
||||
Telemetry.prototype.telemetryInfo = {};
|
||||
Telemetry.prototype._oldlog = Telemetry.prototype.log;
|
||||
Telemetry.prototype.log = function(histogramId, value) {
|
||||
if (histogramId) {
|
||||
if (!this.telemetryInfo[histogramId]) {
|
||||
this.telemetryInfo[histogramId] = [];
|
||||
}
|
||||
|
||||
this.telemetryInfo[histogramId].push(value);
|
||||
}
|
||||
}
|
||||
|
||||
testNextTool();
|
||||
}
|
||||
|
||||
function testNextTool() {
|
||||
let nextTool = tabsToTest.pop();
|
||||
|
||||
if (nextTool) {
|
||||
openToolboxTabTwice(nextTool);
|
||||
}
|
||||
}
|
||||
|
||||
function openToolboxTabTwice(id, secondPass) {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
gDevTools.showToolbox(target, id).then(function(toolbox) {
|
||||
info("Toolbox tab " + id + " opened");
|
||||
|
||||
toolbox.once("destroyed", function() {
|
||||
if (secondPass) {
|
||||
if (tabsToTest.length > 0) {
|
||||
testNextTool();
|
||||
} else {
|
||||
checkResults();
|
||||
}
|
||||
} else {
|
||||
openToolboxTabTwice(id, true);
|
||||
}
|
||||
});
|
||||
// We use a timeout to check the tools active time
|
||||
setTimeout(function() {
|
||||
gDevTools.closeToolbox(target);
|
||||
}, TOOL_DELAY);
|
||||
}).then(null, reportError);
|
||||
}
|
||||
|
||||
function checkResults() {
|
||||
let result = Telemetry.prototype.telemetryInfo;
|
||||
|
||||
for (let [histId, value] of Iterator(result)) {
|
||||
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
|
||||
ok(value.length === 1 && value[0] === true,
|
||||
"Per user value " + histId + " has a single value of true");
|
||||
} else if (histId.endsWith("OPENED_BOOLEAN")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element === true;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries are === true");
|
||||
} else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element > 0;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries have time > 0");
|
||||
}
|
||||
}
|
||||
|
||||
finishUp();
|
||||
}
|
||||
|
||||
function reportError(error) {
|
||||
let stack = " " + error.stack.replace(/\n?.*?@/g, "\n JS frame :: ");
|
||||
|
||||
ok(false, "ERROR: " + error + " at " + error.fileName + ":" +
|
||||
error.lineNumber + "\n\nStack trace:" + stack);
|
||||
finishUp();
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
Telemetry.prototype.log = Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype.telemetryInfo;
|
||||
|
||||
TargetFactory = tabsToTest = Services = Promise = require = null;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
waitForFocus(init, content);
|
||||
}, true);
|
||||
|
||||
content.location = TEST_URI;
|
||||
}
|
@ -11,6 +11,7 @@ const Cu = Components.utils;
|
||||
const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms
|
||||
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["SideMenuWidget"];
|
||||
|
||||
@ -48,6 +49,9 @@ this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) {
|
||||
this._list.setAttribute("flex", "1");
|
||||
this._list.setAttribute("orient", "vertical");
|
||||
this._list.setAttribute("with-arrow", aShowArrows);
|
||||
this._list.setAttribute("tabindex", "0");
|
||||
this._list.addEventListener("keypress", e => this.emit("keyPress", e), true);
|
||||
this._list.addEventListener("mousedown", e => this.emit("mousePress", e), true);
|
||||
this._parent.appendChild(this._list);
|
||||
this._boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
|
||||
|
||||
@ -56,6 +60,9 @@ this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) {
|
||||
this._orderedGroupElementsArray = [];
|
||||
this._orderedMenuElementsArray = [];
|
||||
|
||||
// This widget emits events that can be handled in a MenuContainer.
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
// Delegate some of the associated node's methods to satisfy the interface
|
||||
// required by MenuContainer instances.
|
||||
ViewHelpers.delegateWidgetEventMethods(this, aNode);
|
||||
@ -67,6 +74,14 @@ SideMenuWidget.prototype = {
|
||||
*/
|
||||
sortedGroups: true,
|
||||
|
||||
/**
|
||||
* Specifies if when an item is selected in this container (via the
|
||||
* selectedItem setter), that respective item should be focused as well.
|
||||
* You can enable this flag, for example, to maintain a certain node
|
||||
* selected but visually indicate a different selection in this container.
|
||||
*/
|
||||
preventFocusOnSelection: false,
|
||||
|
||||
/**
|
||||
* Specifies if this container should try to keep the selected item visible.
|
||||
* (For example, when new items are added the selection is brought into view).
|
||||
@ -100,9 +115,17 @@ SideMenuWidget.prototype = {
|
||||
// Invalidate any notices set on this widget.
|
||||
this.removeAttribute("notice");
|
||||
|
||||
// Maintaining scroll position at the bottom when a new item is inserted
|
||||
// depends on several factors (the order of testing is important to avoid
|
||||
// needlessly expensive operations that may cause reflows):
|
||||
let maintainScrollAtBottom =
|
||||
// 1. The behavior should be enabled,
|
||||
this.autoscrollWithAppendedItems &&
|
||||
// 2. There shouldn't currently be any selected item in the list.
|
||||
!this._selectedItem &&
|
||||
// 3. The new item should be appended at the end of the list.
|
||||
(aIndex < 0 || aIndex >= this._orderedMenuElementsArray.length) &&
|
||||
// 4. The list should already be scrolled at the bottom.
|
||||
(this._list.scrollTop + this._list.clientHeight >= this._list.scrollHeight);
|
||||
|
||||
let group = this._getMenuGroupForName(aGroup);
|
||||
@ -112,6 +135,9 @@ SideMenuWidget.prototype = {
|
||||
if (this.maintainSelectionVisible) {
|
||||
this.ensureSelectionIsVisible({ withGroup: true, delayed: true });
|
||||
}
|
||||
if (this._orderedMenuElementsArray.length == 1) {
|
||||
this._list.focus();
|
||||
}
|
||||
if (maintainScrollAtBottom) {
|
||||
this._list.scrollTop = this._list.scrollHeight;
|
||||
}
|
||||
@ -193,6 +219,7 @@ SideMenuWidget.prototype = {
|
||||
node.classList.add("selected");
|
||||
node.parentNode.classList.add("selected");
|
||||
this._selectedItem = node;
|
||||
!this.preventFocusOnSelection && node.focus();
|
||||
} else {
|
||||
node.classList.remove("selected");
|
||||
node.parentNode.classList.remove("selected");
|
||||
|
@ -577,32 +577,6 @@ VariablesView.prototype = {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the first visible scope, variable, or property in this container.
|
||||
*/
|
||||
focusFirstVisibleNode: function() {
|
||||
let focusableItem = this._findInVisibleItems(item => item.focusable);
|
||||
|
||||
if (focusableItem) {
|
||||
this._focusItem(focusableItem);
|
||||
}
|
||||
this._parent.scrollTop = 0;
|
||||
this._parent.scrollLeft = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the last visible scope, variable, or property in this container.
|
||||
*/
|
||||
focusLastVisibleNode: function() {
|
||||
let focusableItem = this._findInVisibleItemsReverse(item => item.focusable);
|
||||
|
||||
if (focusableItem) {
|
||||
this._focusItem(focusableItem);
|
||||
}
|
||||
this._parent.scrollTop = this._parent.scrollHeight;
|
||||
this._parent.scrollLeft = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches for the scope in this container displayed by the specified node.
|
||||
*
|
||||
@ -644,19 +618,58 @@ VariablesView.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the next scope, variable or property in this view.
|
||||
* @see VariablesView.prototype._focusChange
|
||||
* Focuses the first visible scope, variable, or property in this container.
|
||||
*/
|
||||
focusNextItem: function(aMaintainViewFocusedFlag) {
|
||||
this._focusChange("advanceFocus", aMaintainViewFocusedFlag)
|
||||
focusFirstVisibleItem: function() {
|
||||
let focusableItem = this._findInVisibleItems(item => item.focusable);
|
||||
if (focusableItem) {
|
||||
this._focusItem(focusableItem);
|
||||
}
|
||||
this._parent.scrollTop = 0;
|
||||
this._parent.scrollLeft = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the last visible scope, variable, or property in this container.
|
||||
*/
|
||||
focusLastVisibleItem: function() {
|
||||
let focusableItem = this._findInVisibleItemsReverse(item => item.focusable);
|
||||
if (focusableItem) {
|
||||
this._focusItem(focusableItem);
|
||||
}
|
||||
this._parent.scrollTop = this._parent.scrollHeight;
|
||||
this._parent.scrollLeft = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the next scope, variable or property in this view.
|
||||
*/
|
||||
focusNextItem: function() {
|
||||
this.focusItemAtDelta(+1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the previous scope, variable or property in this view.
|
||||
* @see VariablesView.prototype._focusChange
|
||||
*/
|
||||
focusPrevItem: function(aMaintainViewFocusedFlag) {
|
||||
this._focusChange("rewindFocus", aMaintainViewFocusedFlag)
|
||||
focusPrevItem: function() {
|
||||
this.focusItemAtDelta(-1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses another scope, variable or property in this view, based on
|
||||
* the index distance from the currently focused item.
|
||||
*
|
||||
* @param number aDelta
|
||||
* A scalar specifying by how many items should the selection change.
|
||||
*/
|
||||
focusItemAtDelta: function(aDelta) {
|
||||
let direction = aDelta > 0 ? "advanceFocus" : "rewindFocus";
|
||||
let distance = Math.abs(Math[aDelta > 0 ? "ceil" : "floor"](aDelta));
|
||||
while (distance--) {
|
||||
if (!this._focusChange(direction)) {
|
||||
break; // Out of bounds.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -664,41 +677,29 @@ VariablesView.prototype = {
|
||||
*
|
||||
* @param string aDirection
|
||||
* Either "advanceFocus" or "rewindFocus".
|
||||
* @param boolean aMaintainViewFocusedFlag
|
||||
* True too keep this view focused if the element is out of bounds.
|
||||
* @return boolean
|
||||
* True if the focus went out of bounds and the first or last element
|
||||
* False if the focus went out of bounds and the first or last element
|
||||
* in this view was focused instead.
|
||||
*/
|
||||
_focusChange: function(aDirection, aMaintainViewFocusedFlag) {
|
||||
_focusChange: function(aDirection) {
|
||||
let commandDispatcher = this.document.commandDispatcher;
|
||||
let item;
|
||||
let prevFocusedElement = commandDispatcher.focusedElement;
|
||||
let currFocusedItem = null;
|
||||
|
||||
do {
|
||||
commandDispatcher.suppressFocusScroll = true;
|
||||
commandDispatcher[aDirection]();
|
||||
|
||||
// If maintaining this view focused is not mandatory, a simple
|
||||
// "advanceFocus" or "rewindFocus" command dispatch is sufficient.
|
||||
if (!aMaintainViewFocusedFlag) {
|
||||
// Make sure the newly focused item is a part of this view.
|
||||
// If the focus goes out of bounds, revert the previously focused item.
|
||||
if (!(currFocusedItem = this.getFocusedItem())) {
|
||||
prevFocusedElement.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the newly focused target is a part of this view.
|
||||
item = this.getFocusedItem();
|
||||
if (!item) {
|
||||
if (aDirection == "advanceFocus") {
|
||||
this.focusLastVisibleNode();
|
||||
} else {
|
||||
this.focusFirstVisibleNode();
|
||||
}
|
||||
// Focus went out of bounds so the first or last element in this view
|
||||
// was focused instead.
|
||||
return true;
|
||||
}
|
||||
} while (!item.focusable);
|
||||
} while (!currFocusedItem.focusable);
|
||||
|
||||
// Focus remained within bounds.
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -729,19 +730,8 @@ VariablesView.prototype = {
|
||||
_onViewKeyPress: function(e) {
|
||||
let item = this.getFocusedItem();
|
||||
|
||||
switch (e.keyCode) {
|
||||
case e.DOM_VK_UP:
|
||||
case e.DOM_VK_DOWN:
|
||||
case e.DOM_VK_LEFT:
|
||||
case e.DOM_VK_RIGHT:
|
||||
case e.DOM_VK_PAGE_UP:
|
||||
case e.DOM_VK_PAGE_DOWN:
|
||||
case e.DOM_VK_HOME:
|
||||
case e.DOM_VK_END:
|
||||
// Prevent scrolling when pressing navigation keys.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
// Prevent scrolling when pressing navigation keys.
|
||||
ViewHelpers.preventScrolling(e);
|
||||
|
||||
switch (e.keyCode) {
|
||||
case e.DOM_VK_UP:
|
||||
@ -778,36 +768,24 @@ VariablesView.prototype = {
|
||||
|
||||
case e.DOM_VK_PAGE_UP:
|
||||
// Rewind a certain number of elements based on the container height.
|
||||
var jumps = this.pageSize || Math.min(Math.floor(this._list.scrollHeight /
|
||||
this.focusItemAtDelta(-(this.pageSize || Math.min(Math.floor(this._list.scrollHeight /
|
||||
PAGE_SIZE_SCROLL_HEIGHT_RATIO),
|
||||
PAGE_SIZE_MAX_JUMPS);
|
||||
|
||||
while (jumps--) {
|
||||
if (this.focusPrevItem(true)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
PAGE_SIZE_MAX_JUMPS)));
|
||||
return;
|
||||
|
||||
case e.DOM_VK_PAGE_DOWN:
|
||||
// Advance a certain number of elements based on the container height.
|
||||
var jumps = this.pageSize || Math.min(Math.floor(this._list.scrollHeight /
|
||||
this.focusItemAtDelta(+(this.pageSize || Math.min(Math.floor(this._list.scrollHeight /
|
||||
PAGE_SIZE_SCROLL_HEIGHT_RATIO),
|
||||
PAGE_SIZE_MAX_JUMPS);
|
||||
|
||||
while (jumps--) {
|
||||
if (this.focusNextItem(true)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
PAGE_SIZE_MAX_JUMPS)));
|
||||
return;
|
||||
|
||||
case e.DOM_VK_HOME:
|
||||
this.focusFirstVisibleNode();
|
||||
this.focusFirstVisibleItem();
|
||||
return;
|
||||
|
||||
case e.DOM_VK_END:
|
||||
this.focusLastVisibleNode();
|
||||
this.focusLastVisibleItem();
|
||||
return;
|
||||
|
||||
case e.DOM_VK_RETURN:
|
||||
|
@ -10,6 +10,7 @@ const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const PANE_APPEARANCE_DELAY = 50;
|
||||
const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
@ -55,7 +56,7 @@ this.ViewHelpers = {
|
||||
* called preventDefault.
|
||||
*/
|
||||
dispatchEvent: function(aTarget, aType, aDetail) {
|
||||
if (!aTarget) {
|
||||
if (!(aTarget instanceof Ci.nsIDOMNode)) {
|
||||
return true; // Event cancelled.
|
||||
}
|
||||
let document = aTarget.ownerDocument || aTarget;
|
||||
@ -95,6 +96,38 @@ this.ViewHelpers = {
|
||||
aWidget.removeEventListener = aNode.removeEventListener.bind(aNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the specified object looks like it's been decorated by an
|
||||
* event emitter.
|
||||
*
|
||||
* @return boolean
|
||||
* True if it looks, walks and quacks like an event emitter.
|
||||
*/
|
||||
isEventEmitter: function(aObject) {
|
||||
return aObject && aObject.on && aObject.off && aObject.once && aObject.emit;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prevents event propagation when navigation keys are pressed.
|
||||
*
|
||||
* @param Event e
|
||||
* The event to be prevented.
|
||||
*/
|
||||
preventScrolling: function(e) {
|
||||
switch (e.keyCode) {
|
||||
case e.DOM_VK_UP:
|
||||
case e.DOM_VK_DOWN:
|
||||
case e.DOM_VK_LEFT:
|
||||
case e.DOM_VK_RIGHT:
|
||||
case e.DOM_VK_PAGE_UP:
|
||||
case e.DOM_VK_PAGE_DOWN:
|
||||
case e.DOM_VK_HOME:
|
||||
case e.DOM_VK_END:
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a side pane hidden or visible.
|
||||
*
|
||||
@ -207,13 +240,13 @@ ViewHelpers.L10N.prototype = {
|
||||
* L10N shortcut function for numeric arguments that need to be formatted.
|
||||
* All numeric arguments will be fixed to 2 decimals and given a localized
|
||||
* decimal separator. Other arguments will be left alone.
|
||||
*
|
||||
* @param string aName
|
||||
* @param array aArgs
|
||||
* @return string
|
||||
*/
|
||||
getFormatStrWithNumbers: function(aName, ...aArgs) {
|
||||
let newArgs = aArgs.map(x => (typeof x == 'number' ?
|
||||
this.numberWithDecimals(x, 2) : x));
|
||||
let newArgs = aArgs.map(x => typeof x == "number" ? this.numberWithDecimals(x, 2) : x);
|
||||
return this.stringBundle.formatStringFromName(aName, newArgs, newArgs.length);
|
||||
},
|
||||
|
||||
@ -518,6 +551,11 @@ MenuItem.prototype = {
|
||||
* - function removeAttribute(aName:string)
|
||||
* - function addEventListener(aName:string, aCallback:function, aBubbleFlag:boolean)
|
||||
* - function removeEventListener(aName:string, aCallback:function, aBubbleFlag:boolean)
|
||||
*
|
||||
* For automagical keyboard and mouse accessibility, the element node or widget
|
||||
* should be an event emitter with the following events:
|
||||
* - "keyPress" -> (aName:string, aEvent:KeyboardEvent)
|
||||
* - "mousePress" -> (aName:string, aEvent:MouseEvent)
|
||||
*/
|
||||
this.MenuContainer = function MenuContainer() {
|
||||
};
|
||||
@ -533,6 +571,12 @@ MenuContainer.prototype = {
|
||||
this._itemsByValue = new Map(); // itemsByValue because keys are strings,
|
||||
this._itemsByElement = new Map(); // and itemsByElement needs to be iterable.
|
||||
this._stagedItems = [];
|
||||
|
||||
// Handle internal events emitted by the widget if necessary.
|
||||
if (ViewHelpers.isEventEmitter(this._container)) {
|
||||
this._container.on("keyPress", this._onWidgetKeyPress.bind(this));
|
||||
this._container.on("mousePress", this._onWidgetMousePress.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -607,7 +651,7 @@ MenuContainer.prototype = {
|
||||
|
||||
// Sort the items before adding them to this container, if preferred.
|
||||
if (aOptions.sorted) {
|
||||
stagedItems.sort((a, b) => this._sortPredicate(a.item, b.item));
|
||||
stagedItems.sort((a, b) => this._currentSortPredicate(a.item, b.item));
|
||||
}
|
||||
// Append the prepared items to this container.
|
||||
for (let { item, options } of stagedItems) {
|
||||
@ -695,12 +739,32 @@ MenuContainer.prototype = {
|
||||
/**
|
||||
* Toggles all the items in this container hidden or visible.
|
||||
*
|
||||
* This does not change the default filtering predicate, so newly inserted
|
||||
* items will always be visible. Use MenuContainer.prototype.filterContents
|
||||
* if you care.
|
||||
*
|
||||
* @param boolean aVisibleFlag
|
||||
* Specifies the intended visibility.
|
||||
*/
|
||||
toggleContents: function(aVisibleFlag) {
|
||||
for (let [, item] of this._itemsByElement) {
|
||||
item._target.hidden = !aVisibleFlag;
|
||||
for (let [element, item] of this._itemsByElement) {
|
||||
element.hidden = !aVisibleFlag;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles all items in this container hidden or visible based on a predicate.
|
||||
*
|
||||
* @param function aPredicate [optional]
|
||||
* Items are toggled according to the return value of this function,
|
||||
* which will become the new default filtering predicate in this container.
|
||||
* If unspecified, all items will be toggled visible.
|
||||
*/
|
||||
filterContents: function(aPredicate = this._currentFilterPredicate) {
|
||||
this._currentFilterPredicate = aPredicate;
|
||||
|
||||
for (let [element, item] of this._itemsByElement) {
|
||||
element.hidden = !aPredicate(item);
|
||||
}
|
||||
},
|
||||
|
||||
@ -712,8 +776,8 @@ MenuContainer.prototype = {
|
||||
* will become the new default sorting predicate in this container.
|
||||
* If unspecified, all items will be sorted by their label.
|
||||
*/
|
||||
sortContents: function(aPredicate = this._sortPredicate) {
|
||||
let sortedItems = this.allItems.sort(this._sortPredicate = aPredicate);
|
||||
sortContents: function(aPredicate = this._currentSortPredicate) {
|
||||
let sortedItems = this.orderedItems.sort(this._currentSortPredicate = aPredicate);
|
||||
|
||||
for (let i = 0, len = sortedItems.length; i < len; i++) {
|
||||
this.swapItems(this.getItemAtIndex(i), sortedItems[i]);
|
||||
@ -880,14 +944,14 @@ MenuContainer.prototype = {
|
||||
set selectedItem(aItem) {
|
||||
// A falsy item is allowed to invalidate the current selection.
|
||||
let targetElement = aItem ? aItem._target : null;
|
||||
let prevElement = this._container.selectedItem;
|
||||
|
||||
// Prevent selecting the same item again, so return early.
|
||||
if (this._container.selectedItem == targetElement) {
|
||||
if (targetElement == prevElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._container.selectedItem = targetElement;
|
||||
ViewHelpers.dispatchEvent(targetElement, "select", aItem);
|
||||
ViewHelpers.dispatchEvent(targetElement || prevElement, "select", aItem);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -917,6 +981,123 @@ MenuContainer.prototype = {
|
||||
set selectedValue(aValue)
|
||||
this.selectedItem = this._itemsByValue.get(aValue),
|
||||
|
||||
/**
|
||||
* Focuses the first visible item in this container.
|
||||
*/
|
||||
focusFirstVisibleItem: function() {
|
||||
this.focusItemAtDelta(-this.itemCount);
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the last visible item in this container.
|
||||
*/
|
||||
focusLastVisibleItem: function() {
|
||||
this.focusItemAtDelta(+this.itemCount);
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the next item in this container.
|
||||
*/
|
||||
focusNextItem: function() {
|
||||
this.focusItemAtDelta(+1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the previous item in this container.
|
||||
*/
|
||||
focusPrevItem: function() {
|
||||
this.focusItemAtDelta(-1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses another item in this container based on the index distance
|
||||
* from the currently focused item.
|
||||
*
|
||||
* @param number aDelta
|
||||
* A scalar specifying by how many items should the selection change.
|
||||
*/
|
||||
focusItemAtDelta: function(aDelta) {
|
||||
// Make sure the currently selected item is also focused, so that the
|
||||
// command dispatcher mechanism has a relative node to work with.
|
||||
// If there's no selection, just select an item at a corresponding index
|
||||
// (e.g. the first item in this container if aDelta <= 1).
|
||||
let selectedElement = this._container.selectedItem;
|
||||
if (selectedElement) {
|
||||
selectedElement.focus();
|
||||
} else {
|
||||
this.selectedIndex = Math.max(0, aDelta - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
let direction = aDelta > 0 ? "advanceFocus" : "rewindFocus";
|
||||
let distance = Math.abs(Math[aDelta > 0 ? "ceil" : "floor"](aDelta));
|
||||
while (distance--) {
|
||||
if (!this._focusChange(direction)) {
|
||||
break; // Out of bounds.
|
||||
}
|
||||
}
|
||||
|
||||
// Synchronize the selected item as being the currently focused element.
|
||||
this.selectedItem = this.getItemForElement(this._focusedElement);
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses the next or previous item in this container.
|
||||
*
|
||||
* @param string aDirection
|
||||
* Either "advanceFocus" or "rewindFocus".
|
||||
* @return boolean
|
||||
* False if the focus went out of bounds and the first or last item
|
||||
* in this container was focused instead.
|
||||
*/
|
||||
_focusChange: function(aDirection) {
|
||||
let commandDispatcher = this._commandDispatcher;
|
||||
let prevFocusedElement = commandDispatcher.focusedElement;
|
||||
|
||||
commandDispatcher.suppressFocusScroll = true;
|
||||
commandDispatcher[aDirection]();
|
||||
|
||||
// Make sure the newly focused item is a part of this container.
|
||||
// If the focus goes out of bounds, revert the previously focused item.
|
||||
if (!this.getItemForElement(commandDispatcher.focusedElement)) {
|
||||
prevFocusedElement.focus();
|
||||
return false;
|
||||
}
|
||||
// Focus remained within bounds.
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the command dispatcher instance associated with this container's DOM.
|
||||
* If there are no items displayed in this container, null is returned.
|
||||
* @return nsIDOMXULCommandDispatcher | null
|
||||
*/
|
||||
get _commandDispatcher() {
|
||||
if (this._cachedCommandDispatcher) {
|
||||
return this._cachedCommandDispatcher;
|
||||
}
|
||||
let someElement = this._container.getItemAtIndex(0);
|
||||
if (someElement) {
|
||||
let commandDispatcher = someElement.ownerDocument.commandDispatcher;
|
||||
return this._cachedCommandDispatcher = commandDispatcher;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the currently focused element in this container.
|
||||
*
|
||||
* @return nsIDOMNode
|
||||
* The focused element, or null if nothing is found.
|
||||
*/
|
||||
get _focusedElement() {
|
||||
let commandDispatcher = this._commandDispatcher;
|
||||
if (commandDispatcher) {
|
||||
return commandDispatcher.focusedElement;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the item in the container having the specified index.
|
||||
*
|
||||
@ -1038,14 +1219,32 @@ MenuContainer.prototype = {
|
||||
* Returns a list of all items in this container, in the displayed order.
|
||||
* @return array
|
||||
*/
|
||||
get allItems() {
|
||||
get orderedItems() {
|
||||
let items = [];
|
||||
for (let i = 0; i < this.itemCount; i++) {
|
||||
let itemCount = this.itemCount;
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
items.push(this.getItemAtIndex(i));
|
||||
}
|
||||
return items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of all the visible (non-hidden) items in this container,
|
||||
* in the displayed order
|
||||
* @return array
|
||||
*/
|
||||
get orderedVisibleItems() {
|
||||
let items = [];
|
||||
let itemCount = this.itemCount;
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
let item = this.getItemAtIndex(i);
|
||||
if (!item._target.hidden) {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of all the visible (non-hidden) items in this container,
|
||||
* in no particular order.
|
||||
@ -1119,11 +1318,10 @@ MenuContainer.prototype = {
|
||||
* The expected item index.
|
||||
*/
|
||||
_findExpectedIndex: function(aItem) {
|
||||
let container = this._container;
|
||||
let itemCount = this.itemCount;
|
||||
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
if (this._sortPredicate(this.getItemAtIndex(i), aItem) > 0) {
|
||||
if (this._currentSortPredicate(this.getItemAtIndex(i), aItem) > 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@ -1160,6 +1358,9 @@ MenuContainer.prototype = {
|
||||
aItem.attachment));
|
||||
|
||||
// Handle any additional options after entangling the item.
|
||||
if (!this._currentFilterPredicate(aItem)) {
|
||||
aItem._target.hidden = true;
|
||||
}
|
||||
if (aOptions.attributes) {
|
||||
aItem.setAttributes(aOptions.attributes, aItem._target);
|
||||
}
|
||||
@ -1217,6 +1418,74 @@ MenuContainer.prototype = {
|
||||
this._itemsByElement.delete(aItem._target);
|
||||
},
|
||||
|
||||
/**
|
||||
* The number of elements in this container to jump when Page Up or Page Down
|
||||
* keys are pressed. If falsy, then the page size will be based on the
|
||||
* number of visible items in the container.
|
||||
*/
|
||||
pageSize: 0,
|
||||
|
||||
/**
|
||||
* The keyPress event listener for this container.
|
||||
* @param string aName
|
||||
* @param KeyboardEvent aEvent
|
||||
*/
|
||||
_onWidgetKeyPress: function(aName, aEvent) {
|
||||
// Prevent scrolling when pressing navigation keys.
|
||||
ViewHelpers.preventScrolling(aEvent);
|
||||
|
||||
switch (aEvent.keyCode) {
|
||||
case aEvent.DOM_VK_UP:
|
||||
this.focusPrevItem();
|
||||
return;
|
||||
case aEvent.DOM_VK_DOWN:
|
||||
this.focusNextItem();
|
||||
return;
|
||||
case aEvent.DOM_VK_PAGE_UP:
|
||||
this.focusItemAtDelta(-(this.pageSize || (this.itemCount / PAGE_SIZE_ITEM_COUNT_RATIO)));
|
||||
return;
|
||||
case aEvent.DOM_VK_PAGE_DOWN:
|
||||
this.focusItemAtDelta(+(this.pageSize || (this.itemCount / PAGE_SIZE_ITEM_COUNT_RATIO)));
|
||||
return;
|
||||
case aEvent.DOM_VK_HOME:
|
||||
this.focusFirstVisibleItem();
|
||||
return;
|
||||
case aEvent.DOM_VK_END:
|
||||
this.focusLastVisibleItem();
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The keyPress event listener for this container.
|
||||
* @param string aName
|
||||
* @param MouseEvent aEvent
|
||||
*/
|
||||
_onWidgetMousePress: function(aName, aEvent) {
|
||||
if (aEvent.button != 0) {
|
||||
// Only allow left-click to trigger this event.
|
||||
return;
|
||||
}
|
||||
let item = this.getItemForElement(aEvent.target);
|
||||
if (item) {
|
||||
// The container is not empty and we clicked on an actual item.
|
||||
this.selectedItem = item;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The predicate used when filtering items. By default, all items in this
|
||||
* view are visible.
|
||||
*
|
||||
* @param MenuItem aItem
|
||||
* The filtered menu item.
|
||||
* @return boolean
|
||||
* True if the menu item should be visible, false otherwise.
|
||||
*/
|
||||
_currentFilterPredicate: function(aItem) {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* The predicate used when sorting items. By default, items in this view
|
||||
* are sorted by their label.
|
||||
@ -1230,7 +1499,7 @@ MenuContainer.prototype = {
|
||||
* 0 to leave aFirst and aSecond unchanged with respect to each other
|
||||
* 1 to sort aSecond to a lower index than aFirst
|
||||
*/
|
||||
_sortPredicate: function(aFirst, aSecond) {
|
||||
_currentSortPredicate: function(aFirst, aSecond) {
|
||||
return +(aFirst._label.toLowerCase() > aSecond._label.toLowerCase());
|
||||
},
|
||||
|
||||
@ -1239,7 +1508,8 @@ MenuContainer.prototype = {
|
||||
_itemsByLabel: null,
|
||||
_itemsByValue: null,
|
||||
_itemsByElement: null,
|
||||
_preferredValue: null
|
||||
_preferredValue: null,
|
||||
_cachedCommandDispatcher: null
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,10 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.side-menu-widget-item-contents {
|
||||
-moz-user-focus: normal;
|
||||
}
|
||||
|
||||
/* VariablesView */
|
||||
|
||||
.variables-view-container {
|
||||
|
@ -11,6 +11,7 @@ let {TiltVisualizer} = require("devtools/tilt/tilt-visualizer");
|
||||
let TiltGL = require("devtools/tilt/tilt-gl");
|
||||
let TiltUtils = require("devtools/tilt/tilt-utils");
|
||||
let EventEmitter = require("devtools/shared/event-emitter");
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
@ -94,6 +95,8 @@ function Tilt(aWindow)
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.setup();
|
||||
|
||||
this._telemetry = new Telemetry();
|
||||
}
|
||||
|
||||
Tilt.prototype = {
|
||||
@ -115,7 +118,10 @@ Tilt.prototype = {
|
||||
// if the visualizer for the current tab is already open, destroy it now
|
||||
if (this.visualizers[id]) {
|
||||
this.destroy(id, true);
|
||||
this._telemetry.toolClosed("tilt");
|
||||
return;
|
||||
} else {
|
||||
this._telemetry.toolOpened("tilt");
|
||||
}
|
||||
|
||||
// create a visualizer instance for the current tab
|
||||
|
@ -10,7 +10,6 @@ const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
|
||||
@ -37,6 +36,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ViewHelpers",
|
||||
"resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
let Telemetry = devtools.require("devtools/shared/telemetry");
|
||||
|
||||
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
|
||||
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
|
||||
|
||||
@ -515,6 +516,7 @@ WebConsole.prototype = {
|
||||
function BrowserConsole()
|
||||
{
|
||||
WebConsole.apply(this, arguments);
|
||||
this._telemetry = new Telemetry();
|
||||
}
|
||||
|
||||
ViewHelpers.create({ constructor: BrowserConsole, proto: WebConsole.prototype },
|
||||
@ -545,6 +547,7 @@ ViewHelpers.create({ constructor: BrowserConsole, proto: WebConsole.prototype },
|
||||
window.addEventListener("unload", onClose);
|
||||
|
||||
this._bc_init = this.$init().then((aReason) => {
|
||||
this._telemetry.toolOpened("browserconsole");
|
||||
let title = this.ui.rootElement.getAttribute("browserConsoleTitle");
|
||||
this.ui.rootElement.setAttribute("title", title);
|
||||
|
||||
@ -571,6 +574,8 @@ ViewHelpers.create({ constructor: BrowserConsole, proto: WebConsole.prototype },
|
||||
return this._bc_destroyer.promise;
|
||||
}
|
||||
|
||||
this._telemetry.toolClosed("browserconsole");
|
||||
|
||||
this._bc_destroyer = Promise.defer();
|
||||
|
||||
let chromeWindow = this.chromeWindow;
|
||||
|
@ -63,6 +63,42 @@
|
||||
- in the network details pane identifying the timings tab. -->
|
||||
<!ENTITY netmonitorUI.tab.timings "Timings">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterAll): This is the label displayed
|
||||
- in the network details footer for the "All" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterAll "All">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterHTML): This is the label displayed
|
||||
- in the network details footer for the "HTML" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterHTML "HTML">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterCSS): This is the label displayed
|
||||
- in the network details footer for the "CSS" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterCSS "CSS">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterJS): This is the label displayed
|
||||
- in the network details footer for the "JS" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterJS "JS">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterXHR): This is the label displayed
|
||||
- in the network details footer for the "XHR" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterXHR "XHR">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterFonts): This is the label displayed
|
||||
- in the network details footer for the "Fonts" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterFonts "Fonts">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterImages): This is the label displayed
|
||||
- in the network details footer for the "Images" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterImages "Images">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterMedia): This is the label displayed
|
||||
- in the network details footer for the "Media" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterMedia "Media">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterFlash): This is the label displayed
|
||||
- in the network details footer for the "Flash" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterFlash "Flash">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.panesButton.tooltip): This is the tooltip for
|
||||
- the button that toggles the panes visible or hidden in the netmonitor UI. -->
|
||||
<!ENTITY netmonitorUI.panesButton.tooltip "Toggle network info">
|
||||
|
@ -16,9 +16,36 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window id='win'/>";
|
||||
const NEWTAB_URL = "about:newtab";
|
||||
const PREF_BRANCH = "browser.newtab.";
|
||||
const TOPIC_DELAYED_STARTUP = "browser-delayed-startup-finished";
|
||||
|
||||
// The interval between swapping in a preload docShell and kicking off the
|
||||
// next preload in the background.
|
||||
const PRELOADER_INTERVAL_MS = 3000;
|
||||
// The initial delay before we start preloading our first new tab page. The
|
||||
// timer is started after the first 'browser-delayed-startup' has been sent.
|
||||
const PRELOADER_INIT_DELAY_MS = 5000;
|
||||
// The number of miliseconds we'll wait after we received a notification that
|
||||
// causes us to update our list of browsers and tabbrowser sizes. This acts as
|
||||
// kind of a damper when too many events are occuring in quick succession.
|
||||
const PRELOADER_UPDATE_DELAY_MS = 3000;
|
||||
|
||||
const TOPIC_TIMER_CALLBACK = "timer-callback";
|
||||
const TOPIC_DELAYED_STARTUP = "browser-delayed-startup-finished";
|
||||
const TOPIC_XUL_WINDOW_CLOSED = "xul-window-destroyed";
|
||||
|
||||
function createTimer(obj, delay) {
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.init(obj, delay, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
return timer;
|
||||
}
|
||||
|
||||
function clearTimer(timer) {
|
||||
if (timer) {
|
||||
timer.cancel();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
this.BrowserNewTabPreloader = {
|
||||
init: function Preloader_init() {
|
||||
@ -29,11 +56,20 @@ this.BrowserNewTabPreloader = {
|
||||
Initializer.stop();
|
||||
HostFrame.destroy();
|
||||
Preferences.uninit();
|
||||
HiddenBrowser.destroy();
|
||||
HiddenBrowsers.uninit();
|
||||
},
|
||||
|
||||
newTab: function Preloader_newTab(aTab) {
|
||||
HiddenBrowser.swapWithNewTab(aTab);
|
||||
let win = aTab.ownerDocument.defaultView;
|
||||
if (win.gBrowser) {
|
||||
let {boxObject: {width, height}} = win.gBrowser;
|
||||
let hiddenBrowser = HiddenBrowsers.get(width, height)
|
||||
if (hiddenBrowser) {
|
||||
return hiddenBrowser.swapWithNewTab(aTab);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -49,10 +85,7 @@ let Initializer = {
|
||||
},
|
||||
|
||||
stop: function Initializer_stop() {
|
||||
if (this._timer) {
|
||||
this._timer.cancel();
|
||||
this._timer = null;
|
||||
}
|
||||
this._timer = clearTimer(this._timer);
|
||||
|
||||
if (this._observing) {
|
||||
Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
|
||||
@ -65,21 +98,20 @@ let Initializer = {
|
||||
Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
|
||||
this._observing = false;
|
||||
this._startTimer();
|
||||
} else if (aTopic == "timer-callback") {
|
||||
} else if (aTopic == TOPIC_TIMER_CALLBACK) {
|
||||
this._timer = null;
|
||||
this._startPreloader();
|
||||
}
|
||||
},
|
||||
|
||||
_startTimer: function Initializer_startTimer() {
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._timer.init(this, PRELOADER_INIT_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
this._timer = createTimer(this, PRELOADER_INIT_DELAY_MS);
|
||||
},
|
||||
|
||||
_startPreloader: function Initializer_startPreloader() {
|
||||
Preferences.init();
|
||||
if (Preferences.enabled) {
|
||||
HiddenBrowser.create();
|
||||
HiddenBrowsers.init();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -87,26 +119,16 @@ let Initializer = {
|
||||
let Preferences = {
|
||||
_enabled: null,
|
||||
_branch: null,
|
||||
_url: null,
|
||||
|
||||
get enabled() {
|
||||
if (this._enabled === null) {
|
||||
this._enabled = this._branch.getBoolPref("preload") &&
|
||||
!this._branch.prefHasUserValue("url") &&
|
||||
this.url && this.url != "about:blank";
|
||||
!this._branch.prefHasUserValue("url");
|
||||
}
|
||||
|
||||
return this._enabled;
|
||||
},
|
||||
|
||||
get url() {
|
||||
if (this._url === null) {
|
||||
this._url = this._branch.getCharPref("url");
|
||||
}
|
||||
|
||||
return this._url;
|
||||
},
|
||||
|
||||
init: function Preferences_init() {
|
||||
this._branch = Services.prefs.getBranch(PREF_BRANCH);
|
||||
this._branch.addObserver("", this, false);
|
||||
@ -119,56 +141,225 @@ let Preferences = {
|
||||
}
|
||||
},
|
||||
|
||||
observe: function Preferences_observe(aSubject, aTopic, aData) {
|
||||
let {url, enabled} = this;
|
||||
this._url = this._enabled = null;
|
||||
observe: function Preferences_observe() {
|
||||
let prevEnabled = this._enabled;
|
||||
this._enabled = null;
|
||||
|
||||
if (enabled && !this.enabled) {
|
||||
HiddenBrowser.destroy();
|
||||
} else if (!enabled && this.enabled) {
|
||||
HiddenBrowser.create();
|
||||
} else if (this._browser && url != this.url) {
|
||||
HiddenBrowser.update(this.url);
|
||||
if (prevEnabled && !this.enabled) {
|
||||
HiddenBrowsers.uninit();
|
||||
} else if (!prevEnabled && this.enabled) {
|
||||
HiddenBrowsers.init();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let HiddenBrowser = {
|
||||
let HiddenBrowsers = {
|
||||
_browsers: null,
|
||||
_updateTimer: null,
|
||||
|
||||
_topics: [
|
||||
TOPIC_DELAYED_STARTUP,
|
||||
TOPIC_XUL_WINDOW_CLOSED
|
||||
],
|
||||
|
||||
init: function () {
|
||||
this._browsers = new Map();
|
||||
this._updateBrowserSizes();
|
||||
this._topics.forEach(t => Services.obs.addObserver(this, t, false));
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
this._topics.forEach(t => Services.obs.removeObserver(this, t, false));
|
||||
this._updateTimer = clearTimer(this._updateTimer);
|
||||
|
||||
if (this._browsers) {
|
||||
for (let [key, browser] of this._browsers) {
|
||||
browser.destroy();
|
||||
}
|
||||
this._browsers = null;
|
||||
}
|
||||
},
|
||||
|
||||
get: function (width, height) {
|
||||
// We haven't been initialized, yet.
|
||||
if (!this._browsers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let key = width + "x" + height;
|
||||
if (!this._browsers.has(key)) {
|
||||
// Update all browsers' sizes if we can't find a matching one.
|
||||
this._updateBrowserSizes();
|
||||
}
|
||||
|
||||
// We should now have a matching browser.
|
||||
if (this._browsers.has(key)) {
|
||||
return this._browsers.get(key);
|
||||
}
|
||||
|
||||
// We should never be here. Return the first browser we find.
|
||||
Cu.reportError("NewTabPreloader: no matching browser found after updating");
|
||||
for (let [size, browser] of this._browsers) {
|
||||
return browser;
|
||||
}
|
||||
|
||||
// We should really never be here.
|
||||
Cu.reportError("NewTabPreloader: not even a single browser was found?");
|
||||
return null;
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
if (topic === TOPIC_TIMER_CALLBACK) {
|
||||
this._updateTimer = null;
|
||||
this._updateBrowserSizes();
|
||||
} else {
|
||||
this._updateTimer = clearTimer(this._updateTimer);
|
||||
this._updateTimer = createTimer(this, PRELOADER_UPDATE_DELAY_MS);
|
||||
}
|
||||
},
|
||||
|
||||
_updateBrowserSizes: function () {
|
||||
let sizes = this._collectTabBrowserSizes();
|
||||
let toRemove = [];
|
||||
|
||||
// Iterate all browsers and check that they
|
||||
// each can be assigned to one of the sizes.
|
||||
for (let [key, browser] of this._browsers) {
|
||||
if (sizes.has(key)) {
|
||||
// We already have a browser for that size, great!
|
||||
sizes.delete(key);
|
||||
} else {
|
||||
// This browser is superfluous or needs to be resized.
|
||||
toRemove.push(browser);
|
||||
this._browsers.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate all sizes that we couldn't find a browser for.
|
||||
for (let [key, {width, height}] of sizes) {
|
||||
let browser;
|
||||
if (toRemove.length) {
|
||||
// Let's just resize one of the superfluous
|
||||
// browsers and put it back into the map.
|
||||
browser = toRemove.shift();
|
||||
browser.resize(width, height);
|
||||
} else {
|
||||
// No more browsers to reuse, create a new one.
|
||||
browser = new HiddenBrowser(width, height);
|
||||
}
|
||||
|
||||
this._browsers.set(key, browser);
|
||||
}
|
||||
|
||||
// Finally, remove all browsers we don't need anymore.
|
||||
toRemove.forEach(b => b.destroy());
|
||||
},
|
||||
|
||||
_collectTabBrowserSizes: function () {
|
||||
let sizes = new Map();
|
||||
|
||||
function tabBrowsers() {
|
||||
let wins = Services.ww.getWindowEnumerator("navigator:browser");
|
||||
while (wins.hasMoreElements()) {
|
||||
let win = wins.getNext();
|
||||
if (win.gBrowser) {
|
||||
yield win.gBrowser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the sizes of all <tabbrowser>s out there.
|
||||
for (let {boxObject: {width, height}} of tabBrowsers()) {
|
||||
if (width > 0 && height > 0) {
|
||||
let key = width + "x" + height;
|
||||
if (!sizes.has(key)) {
|
||||
sizes.set(key, {width: width, height: height});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sizes;
|
||||
}
|
||||
};
|
||||
|
||||
function HiddenBrowser(width, height) {
|
||||
this.resize(width, height);
|
||||
|
||||
HostFrame.get(aFrame => {
|
||||
let doc = aFrame.document;
|
||||
this._browser = doc.createElementNS(XUL_NS, "browser");
|
||||
this._browser.setAttribute("type", "content");
|
||||
doc.getElementById("win").appendChild(this._browser);
|
||||
this.preload();
|
||||
});
|
||||
}
|
||||
|
||||
HiddenBrowser.prototype = {
|
||||
_width: null,
|
||||
_height: null,
|
||||
_timer: null,
|
||||
|
||||
get isPreloaded() {
|
||||
return this._browser &&
|
||||
this._browser.contentDocument &&
|
||||
this._browser.contentDocument.readyState == "complete" &&
|
||||
this._browser.currentURI.spec == Preferences.url;
|
||||
this._browser.contentDocument.readyState === "complete" &&
|
||||
this._browser.currentURI.spec === NEWTAB_URL;
|
||||
},
|
||||
|
||||
swapWithNewTab: function HiddenBrowser_swapWithNewTab(aTab) {
|
||||
if (this.isPreloaded) {
|
||||
let tabbrowser = aTab.ownerDocument.defaultView.gBrowser;
|
||||
if (tabbrowser) {
|
||||
tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
|
||||
}
|
||||
swapWithNewTab: function (aTab) {
|
||||
if (!this.isPreloaded || this._timer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let tabbrowser = aTab.ownerDocument.defaultView.gBrowser;
|
||||
if (!tabbrowser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Swap docShells.
|
||||
tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
|
||||
|
||||
// Start a timer that will kick off preloading the next newtab page.
|
||||
this._timer = createTimer(this, PRELOADER_INTERVAL_MS);
|
||||
|
||||
// Signal that we swapped docShells.
|
||||
return true;
|
||||
},
|
||||
|
||||
observe: function () {
|
||||
this._timer = null;
|
||||
this.preload();
|
||||
},
|
||||
|
||||
preload: function () {
|
||||
if (!this._browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the browser has the right size.
|
||||
this.resize(this._width, this._height);
|
||||
|
||||
// Start pre-loading the new tab page.
|
||||
this._browser.loadURI(NEWTAB_URL);
|
||||
},
|
||||
|
||||
resize: function (width, height) {
|
||||
if (this._browser) {
|
||||
this._browser.style.width = width + "px";
|
||||
this._browser.style.height = height + "px";
|
||||
} else {
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
}
|
||||
},
|
||||
|
||||
create: function HiddenBrowser_create() {
|
||||
HostFrame.get(aFrame => {
|
||||
let doc = aFrame.document;
|
||||
this._browser = doc.createElementNS(XUL_NS, "browser");
|
||||
this._browser.setAttribute("type", "content");
|
||||
this._browser.setAttribute("src", Preferences.url);
|
||||
doc.getElementById("win").appendChild(this._browser);
|
||||
});
|
||||
},
|
||||
|
||||
update: function HiddenBrowser_update(aURL) {
|
||||
this._browser.setAttribute("src", aURL);
|
||||
},
|
||||
|
||||
destroy: function HiddenBrowser_destroy() {
|
||||
destroy: function () {
|
||||
if (this._browser) {
|
||||
this._browser.parentNode.removeChild(this._browser);
|
||||
this._browser.remove();
|
||||
this._browser = null;
|
||||
}
|
||||
|
||||
this._timer = clearTimer(this._timer);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3,13 +3,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/. */
|
||||
|
||||
/* Network requests table */
|
||||
|
||||
#body {
|
||||
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
|
||||
}
|
||||
|
||||
.requests-menu-empty-notice {
|
||||
/* Network requests table */
|
||||
|
||||
#requests-menu-empty-notice {
|
||||
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
|
||||
padding: 12px;
|
||||
font-size: 110%;
|
||||
@ -17,19 +17,14 @@
|
||||
}
|
||||
|
||||
#requests-menu-toolbar {
|
||||
padding: 0;
|
||||
-moz-padding-start: 4px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.requests-menu-header:first-child {
|
||||
-moz-padding-start: 4px;
|
||||
}
|
||||
|
||||
.requests-menu-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.requests-menu-subitem {
|
||||
padding: 4px;
|
||||
}
|
||||
@ -43,12 +38,13 @@
|
||||
.requests-menu-header-button {
|
||||
-moz-appearance: none;
|
||||
background: none;
|
||||
min-width: 12px;
|
||||
min-height: 31px; /* Remaining 1px comes from border of the button. */
|
||||
min-width: 20px;
|
||||
min-height: 31px; /* Remaining 1px comes from border of the toolbar. */
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 2px 0px;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font-weight: inherit !important;
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
@ -80,13 +76,12 @@
|
||||
/* Network requests table: specific column dimensions */
|
||||
|
||||
.requests-menu-status-and-method {
|
||||
width: 6em;
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
.requests-menu-status {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin: 0px 2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.requests-menu-method {
|
||||
@ -118,6 +113,8 @@
|
||||
|
||||
box.requests-menu-status {
|
||||
background: #fff;
|
||||
-moz-margin-start: 5px;
|
||||
-moz-margin-end: 5px;
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255,255,255,0.4) inset,
|
||||
@ -269,12 +266,14 @@ box.requests-menu-status[code^="5"] {
|
||||
0 0 4px 0 rgba(255,255,255,1.0) inset;
|
||||
}
|
||||
|
||||
/* SideMenuWidget */
|
||||
|
||||
.side-menu-widget-item:nth-child(even) {
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.side-menu-widget-item-contents {
|
||||
padding: 0px 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Network request details */
|
||||
@ -364,6 +363,51 @@ box.requests-menu-status[code^="5"] {
|
||||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
#requests-menu-footer {
|
||||
box-shadow: inset 0 1px 16px hsla(210,8%,5%,.3);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button,
|
||||
.requests-menu-footer-label {
|
||||
min-width: 1em;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 2px 1.5vw;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.requests-menu-footer-spacer,
|
||||
.requests-menu-footer-button {
|
||||
-moz-border-end: 1px solid hsla(210,8%,5%,.25);
|
||||
box-shadow: 1px 0 0 hsla(210,16%,76%,.1);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button {
|
||||
-moz-appearance: none;
|
||||
background: rgba(0,0,0,0.025);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button:hover {
|
||||
background: rgba(0,0,0,0.20);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button:hover:active {
|
||||
background: rgba(0,0,0,0.35);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button:not(:active)[checked] {
|
||||
background-color: rgba(0,0,0,0.25);
|
||||
background-image: radial-gradient(farthest-side at center top, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3));
|
||||
background-size: 100% 1px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.requests-menu-footer-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Responsive sidebar */
|
||||
@media (max-width: 700px) {
|
||||
#requests-menu-toolbar {
|
||||
@ -371,7 +415,13 @@ box.requests-menu-status[code^="5"] {
|
||||
}
|
||||
|
||||
.requests-menu-header-button {
|
||||
min-height: 25px; /* Remaining 1px comes from border of the button. */
|
||||
min-height: 25px; /* Remaining 1px comes from border of the toolbar. */
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.requests-menu-footer-button,
|
||||
.requests-menu-footer-label {
|
||||
padding: 2px 2vw;
|
||||
}
|
||||
|
||||
#details-pane {
|
||||
@ -381,13 +431,12 @@ box.requests-menu-status[code^="5"] {
|
||||
}
|
||||
|
||||
.requests-menu-status-and-method {
|
||||
width: 14vw;
|
||||
width: 16vw;
|
||||
}
|
||||
|
||||
.requests-menu-file,
|
||||
.requests-menu-domain {
|
||||
width: 30vw;
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
.requests-menu-type {
|
||||
|
@ -3,13 +3,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/. */
|
||||
|
||||
/* Network requests table */
|
||||
|
||||
#body {
|
||||
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
|
||||
}
|
||||
|
||||
.requests-menu-empty-notice {
|
||||
/* Network requests table */
|
||||
|
||||
#requests-menu-empty-notice {
|
||||
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
|
||||
padding: 12px;
|
||||
font-size: 110%;
|
||||
@ -17,19 +17,14 @@
|
||||
}
|
||||
|
||||
#requests-menu-toolbar {
|
||||
padding: 0;
|
||||
-moz-padding-start: 4px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.requests-menu-header:first-child {
|
||||
-moz-padding-start: 4px;
|
||||
}
|
||||
|
||||
.requests-menu-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.requests-menu-subitem {
|
||||
padding: 4px;
|
||||
}
|
||||
@ -43,12 +38,13 @@
|
||||
.requests-menu-header-button {
|
||||
-moz-appearance: none;
|
||||
background: none;
|
||||
min-width: 1em;
|
||||
min-height: 31px; /* Remaining 1px comes from border of the button. */
|
||||
min-width: 20px;
|
||||
min-height: 31px; /* Remaining 1px comes from border of the toolbar. */
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 2px 0px;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font-weight: inherit !important;
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
@ -84,9 +80,8 @@
|
||||
}
|
||||
|
||||
.requests-menu-status {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin: 0px 2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.requests-menu-method {
|
||||
@ -118,6 +113,8 @@
|
||||
|
||||
box.requests-menu-status {
|
||||
background: #fff;
|
||||
-moz-margin-start: 5px;
|
||||
-moz-margin-end: 5px;
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255,255,255,0.4) inset,
|
||||
@ -269,12 +266,14 @@ box.requests-menu-status[code^="5"] {
|
||||
0 0 4px 0 rgba(255,255,255,1.0) inset;
|
||||
}
|
||||
|
||||
/* SideMenuWidget */
|
||||
|
||||
.side-menu-widget-item:nth-child(even) {
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.side-menu-widget-item-contents {
|
||||
padding: 0px 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Network request details */
|
||||
@ -364,6 +363,51 @@ box.requests-menu-status[code^="5"] {
|
||||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
#requests-menu-footer {
|
||||
box-shadow: inset 0 1px 16px hsla(210,8%,5%,.3);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button,
|
||||
.requests-menu-footer-label {
|
||||
min-width: 1em;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 2px 1.5vw;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.requests-menu-footer-spacer,
|
||||
.requests-menu-footer-button {
|
||||
-moz-border-end: 1px solid hsla(210,8%,5%,.25);
|
||||
box-shadow: 1px 0 0 hsla(210,16%,76%,.1);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button {
|
||||
-moz-appearance: none;
|
||||
background: rgba(0,0,0,0.025);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button:hover {
|
||||
background: rgba(0,0,0,0.20);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button:hover:active {
|
||||
background: rgba(0,0,0,0.35);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button:not(:active)[checked] {
|
||||
background-color: rgba(0,0,0,0.25);
|
||||
background-image: radial-gradient(farthest-side at center top, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3));
|
||||
background-size: 100% 1px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.requests-menu-footer-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Responsive sidebar */
|
||||
@media (max-width: 700px) {
|
||||
#requests-menu-toolbar {
|
||||
@ -371,7 +415,12 @@ box.requests-menu-status[code^="5"] {
|
||||
}
|
||||
|
||||
.requests-menu-header-button {
|
||||
min-height: 23px; /* Remaining 1px comes from border of the button. */
|
||||
min-height: 23px; /* Remaining 1px comes from border of the toolbar. */
|
||||
}
|
||||
|
||||
.requests-menu-footer-button,
|
||||
.requests-menu-footer-label {
|
||||
padding: 2px 2vw;
|
||||
}
|
||||
|
||||
#details-pane {
|
||||
@ -381,13 +430,12 @@ box.requests-menu-status[code^="5"] {
|
||||
}
|
||||
|
||||
.requests-menu-status-and-method {
|
||||
width: 14vw;
|
||||
width: 16vw;
|
||||
}
|
||||
|
||||
.requests-menu-file,
|
||||
.requests-menu-domain {
|
||||
width: 30vw;
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
.requests-menu-type {
|
||||
|
@ -3,13 +3,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/. */
|
||||
|
||||
/* Network requests table */
|
||||
|
||||
#body {
|
||||
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
|
||||
}
|
||||
|
||||
.requests-menu-empty-notice {
|
||||
/* Network requests table */
|
||||
|
||||
#requests-menu-empty-notice {
|
||||
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
|
||||
padding: 12px;
|
||||
font-size: 110%;
|
||||
@ -17,19 +17,14 @@
|
||||
}
|
||||
|
||||
#requests-menu-toolbar {
|
||||
padding: 0;
|
||||
-moz-padding-start: 4px;
|
||||
height: 33px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.requests-menu-header:first-child {
|
||||
-moz-padding-start: 4px;
|
||||
}
|
||||
|
||||
.requests-menu-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.requests-menu-subitem {
|
||||
padding: 4px;
|
||||
}
|
||||
@ -43,12 +38,13 @@
|
||||
.requests-menu-header-button {
|
||||
-moz-appearance: none;
|
||||
background: none;
|
||||
min-width: 10px;
|
||||
min-height: 32px; /* Remaining 1px comes from border of the button. */
|
||||
min-width: 20px;
|
||||
min-height: 32px; /* Remaining 1px comes from border of the toolbar. */
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 2px 0px;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font-weight: inherit !important;
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
@ -86,7 +82,6 @@
|
||||
.requests-menu-status {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 0px 2px;
|
||||
}
|
||||
|
||||
.requests-menu-method {
|
||||
@ -118,6 +113,8 @@
|
||||
|
||||
box.requests-menu-status {
|
||||
background: #fff;
|
||||
-moz-margin-start: 5px;
|
||||
-moz-margin-end: 5px;
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255,255,255,0.4) inset,
|
||||
@ -269,12 +266,14 @@ box.requests-menu-status[code^="5"] {
|
||||
0 0 4px 0 rgba(255,255,255,1.0) inset;
|
||||
}
|
||||
|
||||
/* SideMenuWidget */
|
||||
|
||||
.side-menu-widget-item:nth-child(even) {
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.side-menu-widget-item-contents {
|
||||
padding: 0px 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Network request details */
|
||||
@ -364,6 +363,51 @@ box.requests-menu-status[code^="5"] {
|
||||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
#requests-menu-footer {
|
||||
box-shadow: inset 0 1px 16px hsla(210,8%,5%,.3);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button,
|
||||
.requests-menu-footer-label {
|
||||
min-width: 1em;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 0px 1.5vw;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.requests-menu-footer-spacer,
|
||||
.requests-menu-footer-button {
|
||||
-moz-border-end: 1px solid hsla(210,8%,5%,.25);
|
||||
box-shadow: 1px 0 0 hsla(210,16%,76%,.1);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button {
|
||||
-moz-appearance: none;
|
||||
background: rgba(0,0,0,0.025);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button:hover {
|
||||
background: rgba(0,0,0,0.20);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button:hover:active {
|
||||
background: rgba(0,0,0,0.35);
|
||||
}
|
||||
|
||||
.requests-menu-footer-button:not(:active)[checked] {
|
||||
background-color: rgba(0,0,0,0.25);
|
||||
background-image: radial-gradient(farthest-side at center top, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3));
|
||||
background-size: 100% 1px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.requests-menu-footer-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Responsive sidebar */
|
||||
@media (max-width: 700px) {
|
||||
#requests-menu-toolbar {
|
||||
@ -371,7 +415,12 @@ box.requests-menu-status[code^="5"] {
|
||||
}
|
||||
|
||||
.requests-menu-header-button {
|
||||
min-height: 23px; /* Remaining 1px comes from border of the button. */
|
||||
min-height: 23px; /* Remaining 1px comes from border of the toolbar. */
|
||||
}
|
||||
|
||||
.requests-menu-footer-button,
|
||||
.requests-menu-footer-label {
|
||||
padding: 0px 2vw;
|
||||
}
|
||||
|
||||
#details-pane {
|
||||
@ -381,13 +430,12 @@ box.requests-menu-status[code^="5"] {
|
||||
}
|
||||
|
||||
.requests-menu-status-and-method {
|
||||
width: 14vw;
|
||||
width: 16vw;
|
||||
}
|
||||
|
||||
.requests-menu-file,
|
||||
.requests-menu-domain {
|
||||
width: 30vw;
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
.requests-menu-type {
|
||||
|
@ -2478,22 +2478,6 @@ frontend::EmitFunctionScript(JSContext *cx, BytecodeEmitter *bce, ParseNode *bod
|
||||
bce->switchToMain();
|
||||
}
|
||||
|
||||
/*
|
||||
* Emit a prologue for run-once scripts which will deoptimize JIT code if
|
||||
* the script ends up running multiple times via foo.caller related
|
||||
* shenanigans.
|
||||
*/
|
||||
bool runOnce = bce->parent &&
|
||||
bce->parent->emittingRunOnceLambda &&
|
||||
!funbox->argumentsHasLocalBinding() &&
|
||||
!funbox->isGenerator();
|
||||
if (runOnce) {
|
||||
bce->switchToProlog();
|
||||
if (!Emit1(cx, bce, JSOP_RUNONCE) < 0)
|
||||
return false;
|
||||
bce->switchToMain();
|
||||
}
|
||||
|
||||
if (!EmitTree(cx, bce, body))
|
||||
return false;
|
||||
|
||||
@ -2511,10 +2495,8 @@ frontend::EmitFunctionScript(JSContext *cx, BytecodeEmitter *bce, ParseNode *bod
|
||||
* If this function is only expected to run once, mark the script so that
|
||||
* initializers created within it may be given more precise types.
|
||||
*/
|
||||
if (runOnce) {
|
||||
if (bce->parent && bce->parent->emittingRunOnceLambda)
|
||||
bce->script->treatAsRunOnce = true;
|
||||
JS_ASSERT(!bce->script->hasRunOnce);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark as singletons any function which will only be executed once, or
|
||||
|
@ -1669,22 +1669,14 @@ BaselineCompiler::emit_JSOP_DELPROP()
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BaselineCompiler::getScopeCoordinateObject(Register reg)
|
||||
Address
|
||||
BaselineCompiler::getScopeCoordinateAddress(Register reg)
|
||||
{
|
||||
ScopeCoordinate sc(pc);
|
||||
|
||||
masm.loadPtr(frame.addressOfScopeChain(), reg);
|
||||
for (unsigned i = sc.hops; i; i--)
|
||||
masm.extractObject(Address(reg, ScopeObject::offsetOfEnclosingScope()), reg);
|
||||
}
|
||||
|
||||
Address
|
||||
BaselineCompiler::getScopeCoordinateAddress(Register reg)
|
||||
{
|
||||
getScopeCoordinateObject(reg);
|
||||
|
||||
ScopeCoordinate sc(pc);
|
||||
|
||||
Shape *shape = ScopeCoordinateToStaticScopeShape(cx, script, pc);
|
||||
Address addr;
|
||||
@ -1721,29 +1713,6 @@ BaselineCompiler::emit_JSOP_CALLALIASEDVAR()
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_SETALIASEDVAR()
|
||||
{
|
||||
JSScript *outerScript = ScopeCoordinateFunctionScript(cx, script, pc);
|
||||
if (outerScript && outerScript->treatAsRunOnce) {
|
||||
// Type updates for this operation might need to be tracked, so treat
|
||||
// this as a SETPROP.
|
||||
|
||||
// Load rhs into R1.
|
||||
frame.syncStack(1);
|
||||
frame.popValue(R1);
|
||||
|
||||
// Load and box lhs into R0.
|
||||
getScopeCoordinateObject(R2.scratchReg());
|
||||
masm.tagValue(JSVAL_TYPE_OBJECT, R2.scratchReg(), R0);
|
||||
|
||||
// Call SETPROP IC.
|
||||
ICSetProp_Fallback::Compiler compiler(cx);
|
||||
if (!emitOpIC(compiler.getStub(&stubSpace_)))
|
||||
return false;
|
||||
|
||||
// The IC will return the RHS value in R0, mark it as pushed value.
|
||||
frame.push(R0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sync everything except the top value, so that we can use R0 as scratch
|
||||
// (storeValue does not touch it if the top value is in R0).
|
||||
frame.syncStack(1);
|
||||
@ -2469,23 +2438,6 @@ BaselineCompiler::emit_JSOP_ARGUMENTS()
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (*RunOnceScriptPrologueFn)(JSContext *, HandleScript);
|
||||
static const VMFunction RunOnceScriptPrologueInfo =
|
||||
FunctionInfo<RunOnceScriptPrologueFn>(js::RunOnceScriptPrologue);
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_RUNONCE()
|
||||
{
|
||||
frame.syncStack(0);
|
||||
|
||||
prepareVMCall();
|
||||
|
||||
masm.movePtr(ImmGCPtr(script), R0.scratchReg());
|
||||
pushArg(R0.scratchReg());
|
||||
|
||||
return callVM(RunOnceScriptPrologueInfo);
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_REST()
|
||||
{
|
||||
|
@ -152,7 +152,6 @@ namespace ion {
|
||||
_(JSOP_EXCEPTION) \
|
||||
_(JSOP_DEBUGGER) \
|
||||
_(JSOP_ARGUMENTS) \
|
||||
_(JSOP_RUNONCE) \
|
||||
_(JSOP_REST) \
|
||||
_(JSOP_TOID) \
|
||||
_(JSOP_TABLESWITCH) \
|
||||
@ -237,7 +236,6 @@ class BaselineCompiler : public BaselineCompilerSpecific
|
||||
|
||||
bool addPCMappingEntry(bool addIndexEntry);
|
||||
|
||||
void getScopeCoordinateObject(Register reg);
|
||||
Address getScopeCoordinateAddress(Register reg);
|
||||
};
|
||||
|
||||
|
@ -6191,17 +6191,9 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub,
|
||||
JSOp op = JSOp(*pc);
|
||||
FallbackICSpew(cx, stub, "SetProp(%s)", js_CodeName[op]);
|
||||
|
||||
JS_ASSERT(op == JSOP_SETPROP ||
|
||||
op == JSOP_SETNAME ||
|
||||
op == JSOP_SETGNAME ||
|
||||
op == JSOP_INITPROP ||
|
||||
op == JSOP_SETALIASEDVAR);
|
||||
JS_ASSERT(op == JSOP_SETPROP || op == JSOP_SETNAME || op == JSOP_SETGNAME || op == JSOP_INITPROP);
|
||||
|
||||
RootedPropertyName name(cx);
|
||||
if (op == JSOP_SETALIASEDVAR)
|
||||
name = ScopeCoordinateName(cx, script, pc);
|
||||
else
|
||||
name = script->getName(pc);
|
||||
RootedPropertyName name(cx, script->getName(pc));
|
||||
RootedId id(cx, NameToId(name));
|
||||
|
||||
RootedObject obj(cx, ToObjectFromStack(cx, lhs));
|
||||
@ -6217,8 +6209,6 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub,
|
||||
} else if (op == JSOP_SETNAME || op == JSOP_SETGNAME) {
|
||||
if (!SetNameOperation(cx, script, pc, obj, rhs))
|
||||
return false;
|
||||
} else if (op == JSOP_SETALIASEDVAR) {
|
||||
obj->asScope().setAliasedVar(cx, pc, name, rhs);
|
||||
} else if (script->strict) {
|
||||
if (!js::SetProperty<true>(cx, obj, id, rhs))
|
||||
return false;
|
||||
|
@ -2806,7 +2806,7 @@ CodeGenerator::visitNewDeclEnvObject(LNewDeclEnvObject *lir)
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef JSObject *(*NewCallObjectFn)(JSContext *, HandleScript, HandleShape,
|
||||
typedef JSObject *(*NewCallObjectFn)(JSContext *, HandleShape,
|
||||
HandleTypeObject, HeapSlot *);
|
||||
static const VMFunction NewCallObjectInfo =
|
||||
FunctionInfo<NewCallObjectFn>(NewCallObject);
|
||||
@ -2822,33 +2822,25 @@ CodeGenerator::visitNewCallObject(LNewCallObject *lir)
|
||||
OutOfLineCode *ool;
|
||||
if (lir->slots()->isRegister()) {
|
||||
ool = oolCallVM(NewCallObjectInfo, lir,
|
||||
(ArgList(), ImmGCPtr(lir->mir()->block()->info().script()),
|
||||
ImmGCPtr(templateObj->lastProperty()),
|
||||
ImmGCPtr(templateObj->hasLazyType() ? NULL : templateObj->type()),
|
||||
(ArgList(), ImmGCPtr(templateObj->lastProperty()),
|
||||
ImmGCPtr(templateObj->type()),
|
||||
ToRegister(lir->slots())),
|
||||
StoreRegisterTo(obj));
|
||||
} else {
|
||||
ool = oolCallVM(NewCallObjectInfo, lir,
|
||||
(ArgList(), ImmGCPtr(lir->mir()->block()->info().script()),
|
||||
ImmGCPtr(templateObj->lastProperty()),
|
||||
ImmGCPtr(templateObj->hasLazyType() ? NULL : templateObj->type()),
|
||||
(ArgList(), ImmGCPtr(templateObj->lastProperty()),
|
||||
ImmGCPtr(templateObj->type()),
|
||||
ImmWord((void *)NULL)),
|
||||
StoreRegisterTo(obj));
|
||||
}
|
||||
if (!ool)
|
||||
return false;
|
||||
|
||||
if (lir->mir()->needsSingletonType()) {
|
||||
// Objects can only be given singleton types in VM calls.
|
||||
masm.jump(ool->entry());
|
||||
} else {
|
||||
masm.newGCThing(obj, templateObj, ool->entry());
|
||||
masm.initGCThing(obj, templateObj);
|
||||
|
||||
if (lir->slots()->isRegister())
|
||||
masm.storePtr(ToRegister(lir->slots()), Address(obj, JSObject::offsetOfSlots()));
|
||||
}
|
||||
masm.newGCThing(obj, templateObj, ool->entry());
|
||||
masm.initGCThing(obj, templateObj);
|
||||
|
||||
if (lir->slots()->isRegister())
|
||||
masm.storePtr(ToRegister(lir->slots()), Address(obj, JSObject::offsetOfSlots()));
|
||||
masm.bind(ool->rejoin());
|
||||
return true;
|
||||
}
|
||||
@ -5015,17 +5007,6 @@ CodeGenerator::visitGetArgument(LGetArgument *lir)
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (*RunOnceScriptPrologueFn)(JSContext *, HandleScript);
|
||||
static const VMFunction RunOnceScriptPrologueInfo =
|
||||
FunctionInfo<RunOnceScriptPrologueFn>(js::RunOnceScriptPrologue);
|
||||
|
||||
bool
|
||||
CodeGenerator::visitRunOncePrologue(LRunOncePrologue *lir)
|
||||
{
|
||||
pushArg(ImmGCPtr(lir->mir()->block()->info().script()));
|
||||
return callVM(RunOnceScriptPrologueInfo, lir);
|
||||
}
|
||||
|
||||
bool
|
||||
CodeGenerator::emitRest(LInstruction *lir, Register array, Register numActuals,
|
||||
Register temp0, Register temp1, unsigned numFormals,
|
||||
@ -5703,7 +5684,7 @@ CodeGenerator::visitBindNameIC(OutOfLineUpdateCache *ool, BindNameIC *ic)
|
||||
}
|
||||
|
||||
typedef bool (*SetPropertyFn)(JSContext *, HandleObject,
|
||||
HandlePropertyName, const HandleValue, bool, int);
|
||||
HandlePropertyName, const HandleValue, bool, bool);
|
||||
static const VMFunction SetPropertyInfo =
|
||||
FunctionInfo<SetPropertyFn>(SetProperty);
|
||||
|
||||
@ -5713,9 +5694,10 @@ CodeGenerator::visitCallSetProperty(LCallSetProperty *ins)
|
||||
ConstantOrRegister value = TypedOrValueRegister(ToValue(ins, LCallSetProperty::Value));
|
||||
|
||||
const Register objReg = ToRegister(ins->getOperand(0));
|
||||
JSOp op = JSOp(*ins->mir()->resumePoint()->pc());
|
||||
jsbytecode *pc = ins->mir()->resumePoint()->pc();
|
||||
bool isSetName = JSOp(*pc) == JSOP_SETNAME || JSOp(*pc) == JSOP_SETGNAME;
|
||||
|
||||
pushArg(Imm32(op));
|
||||
pushArg(Imm32(isSetName));
|
||||
pushArg(Imm32(ins->mir()->strict()));
|
||||
|
||||
pushArg(value);
|
||||
|
@ -215,7 +215,6 @@ class CodeGenerator : public CodeGeneratorSpecific
|
||||
bool visitIteratorEnd(LIteratorEnd *lir);
|
||||
bool visitArgumentsLength(LArgumentsLength *lir);
|
||||
bool visitGetArgument(LGetArgument *lir);
|
||||
bool visitRunOncePrologue(LRunOncePrologue *lir);
|
||||
bool emitRest(LInstruction *lir, Register array, Register numActuals,
|
||||
Register temp0, Register temp1, unsigned numFormals,
|
||||
JSObject *templateObject, const VMFunction &f);
|
||||
|
@ -1191,9 +1191,6 @@ IonBuilder::inspectOpcode(JSOp op)
|
||||
case JSOP_ARGUMENTS:
|
||||
return jsop_arguments();
|
||||
|
||||
case JSOP_RUNONCE:
|
||||
return jsop_runonce();
|
||||
|
||||
case JSOP_REST:
|
||||
return jsop_rest();
|
||||
|
||||
@ -1305,8 +1302,7 @@ IonBuilder::inspectOpcode(JSOp op)
|
||||
case JSOP_CALLGNAME:
|
||||
{
|
||||
RootedPropertyName name(cx, info().getAtom(pc)->asPropertyName());
|
||||
RootedObject obj(cx, &script()->global());
|
||||
return getStaticName(obj, name);
|
||||
return jsop_getgname(name);
|
||||
}
|
||||
|
||||
case JSOP_BINDGNAME:
|
||||
@ -1315,8 +1311,7 @@ IonBuilder::inspectOpcode(JSOp op)
|
||||
case JSOP_SETGNAME:
|
||||
{
|
||||
RootedPropertyName name(cx, info().getAtom(pc)->asPropertyName());
|
||||
RootedObject obj(cx, &script()->global());
|
||||
return setStaticName(obj, name);
|
||||
return jsop_setgname(name);
|
||||
}
|
||||
|
||||
case JSOP_NAME:
|
||||
@ -4284,9 +4279,8 @@ IonBuilder::createCallObject(MDefinition *callee, MDefinition *scope)
|
||||
|
||||
// Allocate the actual object. It is important that no intervening
|
||||
// instructions could potentially bailout, thus leaking the dynamic slots
|
||||
// pointer. Run-once scripts need a singleton type, so always do a VM call
|
||||
// in such cases.
|
||||
MInstruction *callObj = MNewCallObject::New(templateObj, script()->treatAsRunOnce, slots);
|
||||
// pointer.
|
||||
MInstruction *callObj = MNewCallObject::New(templateObj, slots);
|
||||
current->add(callObj);
|
||||
|
||||
// Initialize the object's reserved slots. No post barrier is needed here,
|
||||
@ -5906,45 +5900,44 @@ IonBuilder::pushTypeBarrier(MInstruction *ins, types::StackTypeSet *observed, bo
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::getStaticName(HandleObject staticObject, HandlePropertyName name)
|
||||
IonBuilder::jsop_getgname(HandlePropertyName name)
|
||||
{
|
||||
JS_ASSERT(staticObject->isGlobal() || staticObject->isCall());
|
||||
// Optimize undefined, NaN, and Infinity.
|
||||
if (name == cx->names().undefined)
|
||||
return pushConstant(UndefinedValue());
|
||||
if (name == cx->names().NaN)
|
||||
return pushConstant(cx->runtime->NaNValue);
|
||||
if (name == cx->names().Infinity)
|
||||
return pushConstant(cx->runtime->positiveInfinityValue);
|
||||
|
||||
if (staticObject->isGlobal()) {
|
||||
// Optimize undefined, NaN, and Infinity.
|
||||
if (name == cx->names().undefined)
|
||||
return pushConstant(UndefinedValue());
|
||||
if (name == cx->names().NaN)
|
||||
return pushConstant(cx->runtime->NaNValue);
|
||||
if (name == cx->names().Infinity)
|
||||
return pushConstant(cx->runtime->positiveInfinityValue);
|
||||
}
|
||||
RootedObject globalObj(cx, &script()->global());
|
||||
JS_ASSERT(globalObj->isNative());
|
||||
|
||||
RootedId id(cx, NameToId(name));
|
||||
|
||||
// For the fastest path, the property must be found, and it must be found
|
||||
// as a normal data property on exactly the global object.
|
||||
RootedShape shape(cx, staticObject->nativeLookup(cx, id));
|
||||
RootedShape shape(cx, globalObj->nativeLookup(cx, id));
|
||||
if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
|
||||
return jsop_getname(name);
|
||||
|
||||
types::TypeObject *staticType = staticObject->getType(cx);
|
||||
if (!staticType)
|
||||
types::TypeObject *globalType = globalObj->getType(cx);
|
||||
if (!globalType)
|
||||
return false;
|
||||
types::HeapTypeSet *propertyTypes = NULL;
|
||||
if (!staticType->unknownProperties()) {
|
||||
propertyTypes = staticType->getProperty(cx, id, false);
|
||||
if (!globalType->unknownProperties()) {
|
||||
propertyTypes = globalType->getProperty(cx, id, false);
|
||||
if (!propertyTypes)
|
||||
return false;
|
||||
}
|
||||
if (propertyTypes && propertyTypes->isOwnProperty(cx, staticType, true)) {
|
||||
if (propertyTypes && propertyTypes->isOwnProperty(cx, globalType, true)) {
|
||||
// The property has been reconfigured as non-configurable, non-enumerable
|
||||
// or non-writable.
|
||||
return jsop_getname(name);
|
||||
}
|
||||
|
||||
types::StackTypeSet *types = types::TypeScript::BytecodeTypes(script(), pc);
|
||||
bool barrier = PropertyReadNeedsTypeBarrier(cx, staticType, name, types);
|
||||
bool barrier = PropertyReadNeedsTypeBarrier(cx, globalType, name, types);
|
||||
|
||||
// If the property is permanent, a shape guard isn't necessary.
|
||||
|
||||
@ -5955,7 +5948,7 @@ IonBuilder::getStaticName(HandleObject staticObject, HandlePropertyName name)
|
||||
if (singleton) {
|
||||
// Try to inline a known constant value.
|
||||
bool isKnownConstant;
|
||||
if (!TestSingletonProperty(cx, staticObject, singleton, id, &isKnownConstant))
|
||||
if (!TestSingletonProperty(cx, globalObj, singleton, id, &isKnownConstant))
|
||||
return false;
|
||||
if (isKnownConstant)
|
||||
return pushConstant(ObjectValue(*singleton));
|
||||
@ -5966,19 +5959,27 @@ IonBuilder::getStaticName(HandleObject staticObject, HandlePropertyName name)
|
||||
return pushConstant(NullValue());
|
||||
}
|
||||
|
||||
MInstruction *obj = MConstant::New(ObjectValue(*staticObject));
|
||||
current->add(obj);
|
||||
MInstruction *global = MConstant::New(ObjectValue(*globalObj));
|
||||
current->add(global);
|
||||
|
||||
// If we have a property typeset, the isOwnProperty call will trigger recompilation if
|
||||
// the property is deleted or reconfigured.
|
||||
if (!propertyTypes && shape->configurable())
|
||||
obj = addShapeGuard(obj, staticObject->lastProperty(), Bailout_ShapeGuard);
|
||||
global = addShapeGuard(global, globalObj->lastProperty(), Bailout_ShapeGuard);
|
||||
|
||||
MIRType rvalType = MIRTypeFromValueType(types->getKnownTypeTag());
|
||||
if (barrier)
|
||||
rvalType = MIRType_Value;
|
||||
JS_ASSERT(shape->slot() >= globalObj->numFixedSlots());
|
||||
|
||||
return loadSlot(obj, shape, rvalType, barrier, types);
|
||||
MSlots *slots = MSlots::New(global);
|
||||
current->add(slots);
|
||||
MLoadSlot *load = MLoadSlot::New(slots, shape->slot() - globalObj->numFixedSlots());
|
||||
current->add(load);
|
||||
|
||||
// Slot loads can be typed, if they have a single, known, definitive type.
|
||||
if (knownType != JSVAL_TYPE_UNKNOWN && !barrier)
|
||||
load->setResultType(MIRTypeFromValueType(knownType));
|
||||
|
||||
current->push(load);
|
||||
return pushTypeBarrier(load, types, barrier);
|
||||
}
|
||||
|
||||
// Whether 'types' includes all possible values represented by input/inputTypes.
|
||||
@ -6015,33 +6016,34 @@ ion::NeedsPostBarrier(CompileInfo &info, MDefinition *value)
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::setStaticName(HandleObject staticObject, HandlePropertyName name)
|
||||
IonBuilder::jsop_setgname(HandlePropertyName name)
|
||||
{
|
||||
RootedObject globalObj(cx, &script()->global());
|
||||
RootedId id(cx, NameToId(name));
|
||||
|
||||
JS_ASSERT(staticObject->isGlobal() || staticObject->isCall());
|
||||
JS_ASSERT(globalObj->isNative());
|
||||
|
||||
MDefinition *value = current->peek(-1);
|
||||
|
||||
if (staticObject->watched())
|
||||
if (globalObj->watched())
|
||||
return jsop_setprop(name);
|
||||
|
||||
// For the fastest path, the property must be found, and it must be found
|
||||
// as a normal data property on exactly the global object.
|
||||
RootedShape shape(cx, staticObject->nativeLookup(cx, id));
|
||||
RootedShape shape(cx, globalObj->nativeLookup(cx, id));
|
||||
if (!shape || !shape->hasDefaultSetter() || !shape->writable() || !shape->hasSlot())
|
||||
return jsop_setprop(name);
|
||||
|
||||
types::TypeObject *staticType = staticObject->getType(cx);
|
||||
if (!staticType)
|
||||
types::TypeObject *globalType = globalObj->getType(cx);
|
||||
if (!globalType)
|
||||
return false;
|
||||
types::HeapTypeSet *propertyTypes = NULL;
|
||||
if (!staticType->unknownProperties()) {
|
||||
propertyTypes = staticType->getProperty(cx, id, false);
|
||||
if (!globalType->unknownProperties()) {
|
||||
propertyTypes = globalType->getProperty(cx, id, false);
|
||||
if (!propertyTypes)
|
||||
return false;
|
||||
}
|
||||
if (!propertyTypes || propertyTypes->isOwnProperty(cx, staticType, true)) {
|
||||
if (!propertyTypes || propertyTypes->isOwnProperty(cx, globalType, true)) {
|
||||
// The property has been reconfigured as non-configurable, non-enumerable
|
||||
// or non-writable.
|
||||
return jsop_setprop(name);
|
||||
@ -6049,36 +6051,48 @@ IonBuilder::setStaticName(HandleObject staticObject, HandlePropertyName name)
|
||||
if (!TypeSetIncludes(propertyTypes, value->type(), value->resultTypeSet()))
|
||||
return jsop_setprop(name);
|
||||
|
||||
current->pop();
|
||||
|
||||
// Pop the bound object on the stack.
|
||||
MDefinition *obj = current->pop();
|
||||
JS_ASSERT(&obj->toConstant()->value().toObject() == staticObject);
|
||||
MInstruction *global = MConstant::New(ObjectValue(*globalObj));
|
||||
current->add(global);
|
||||
|
||||
// If we have a property type set, the isOwnProperty call will trigger recompilation
|
||||
// if the property is deleted or reconfigured. Without TI, we always need a shape guard
|
||||
// to guard against the property being reconfigured as non-writable.
|
||||
if (!propertyTypes)
|
||||
obj = addShapeGuard(obj, staticObject->lastProperty(), Bailout_ShapeGuard);
|
||||
global = addShapeGuard(global, globalObj->lastProperty(), Bailout_ShapeGuard);
|
||||
|
||||
JS_ASSERT(shape->slot() >= globalObj->numFixedSlots());
|
||||
|
||||
MSlots *slots = MSlots::New(global);
|
||||
current->add(slots);
|
||||
|
||||
// Note: we do not use a post barrier when writing to the global object.
|
||||
// Slots in the global object will be treated as roots during a minor GC.
|
||||
if (!staticObject->isGlobal() && NeedsPostBarrier(info(), value))
|
||||
current->add(MPostWriteBarrier::New(obj, value));
|
||||
current->pop();
|
||||
MStoreSlot *store = MStoreSlot::New(slots, shape->slot() - globalObj->numFixedSlots(), value);
|
||||
current->add(store);
|
||||
|
||||
// Determine whether write barrier is required.
|
||||
if (!propertyTypes || propertyTypes->needsBarrier(cx))
|
||||
store->setNeedsBarrier();
|
||||
|
||||
// Pop the global object pushed by bindgname.
|
||||
DebugOnly<MDefinition *> pushedGlobal = current->pop();
|
||||
JS_ASSERT(&pushedGlobal->toConstant()->value().toObject() == globalObj);
|
||||
|
||||
// If the property has a known type, we may be able to optimize typed stores by not
|
||||
// storing the type tag. This only works if the property does not have its initial
|
||||
// |undefined| value; if |undefined| is assigned at a later point, it will be added
|
||||
// to the type set.
|
||||
MIRType slotType = MIRType_None;
|
||||
if (propertyTypes && !staticObject->getSlot(shape->slot()).isUndefined()) {
|
||||
if (propertyTypes && !globalObj->getSlot(shape->slot()).isUndefined()) {
|
||||
JSValueType knownType = propertyTypes->getKnownTypeTag(cx);
|
||||
if (knownType != JSVAL_TYPE_UNKNOWN)
|
||||
slotType = MIRTypeFromValueType(knownType);
|
||||
store->setSlotType(MIRTypeFromValueType(knownType));
|
||||
}
|
||||
|
||||
bool needsBarrier = !propertyTypes || propertyTypes->needsBarrier(cx);
|
||||
return storeSlot(obj, shape, value, needsBarrier, slotType);
|
||||
JS_ASSERT_IF(store->needsBarrier(), store->slotType() != MIRType_None);
|
||||
|
||||
current->push(value);
|
||||
return resumeAfter(store);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -7004,14 +7018,6 @@ GetDefiniteSlot(JSContext *cx, types::StackTypeSet *types, JSAtom *atom)
|
||||
return propertyTypes;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::jsop_runonce()
|
||||
{
|
||||
MRunOncePrologue *ins = MRunOncePrologue::New();
|
||||
current->add(ins);
|
||||
return resumeAfter(ins);
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::jsop_not()
|
||||
{
|
||||
@ -7358,8 +7364,7 @@ IonBuilder::loadSlot(MDefinition *obj, Shape *shape, MIRType rvalType,
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool needsBarrier,
|
||||
MIRType slotType /* = MIRType_None */)
|
||||
IonBuilder::storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool needsBarrier)
|
||||
{
|
||||
JS_ASSERT(shape->hasDefaultSetter());
|
||||
JS_ASSERT(shape->writable());
|
||||
@ -7382,8 +7387,6 @@ IonBuilder::storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool n
|
||||
current->push(value);
|
||||
if (needsBarrier)
|
||||
store->setNeedsBarrier();
|
||||
if (slotType != MIRType_None)
|
||||
store->setSlotType(slotType);
|
||||
return resumeAfter(store);
|
||||
}
|
||||
|
||||
@ -8092,70 +8095,9 @@ IonBuilder::walkScopeChain(unsigned hops)
|
||||
return scope;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::hasStaticScopeObject(ScopeCoordinate sc, MutableHandleObject pcall)
|
||||
{
|
||||
JSScript *outerScript = ScopeCoordinateFunctionScript(cx, script(), pc);
|
||||
if (!outerScript || !outerScript->treatAsRunOnce)
|
||||
return false;
|
||||
|
||||
types::TypeObject *funType = outerScript->function()->getType(cx);
|
||||
if (!funType)
|
||||
return false;
|
||||
if (types::HeapTypeSet::HasObjectFlags(cx, funType, types::OBJECT_FLAG_RUNONCE_INVALIDATED))
|
||||
return false;
|
||||
|
||||
// The script this aliased var operation is accessing will run only once,
|
||||
// so there will be only one call object and the aliased var access can be
|
||||
// compiled in the same manner as a global access. We still need to find
|
||||
// the call object though.
|
||||
|
||||
// Look for the call object on the current script's function's scope chain.
|
||||
// If the current script is inner to the outer script and the function has
|
||||
// singleton type then it should show up here.
|
||||
|
||||
MDefinition *scope = current->getSlot(info().scopeChainSlot());
|
||||
scope->setFoldedUnchecked();
|
||||
|
||||
JSObject *environment = script()->function()->environment();
|
||||
while (environment && !environment->isGlobal()) {
|
||||
if (environment->isCall() &&
|
||||
!environment->asCall().isForEval() &&
|
||||
environment->asCall().callee().nonLazyScript() == outerScript)
|
||||
{
|
||||
JS_ASSERT(environment->hasSingletonType());
|
||||
pcall.set(environment);
|
||||
return true;
|
||||
}
|
||||
environment = environment->enclosingScope();
|
||||
}
|
||||
|
||||
// Look for the call object on the current frame, if we are compiling the
|
||||
// outer script itself. Don't do this if we are at entry to the outer
|
||||
// script, as the call object we see will not be the real one --- after
|
||||
// entering the Ion code a different call object will be created.
|
||||
|
||||
if (script() == outerScript && fp && info().osrPc()) {
|
||||
JSObject *scope = fp.scopeChain();
|
||||
if (scope->isCall() && scope->asCall().callee().nonLazyScript() == outerScript) {
|
||||
JS_ASSERT(scope->hasSingletonType());
|
||||
pcall.set(scope);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::jsop_getaliasedvar(ScopeCoordinate sc)
|
||||
{
|
||||
RootedObject call(cx);
|
||||
if (hasStaticScopeObject(sc, &call) && call) {
|
||||
RootedPropertyName name(cx, ScopeCoordinateName(cx, script(), pc));
|
||||
return getStaticName(call, name);
|
||||
}
|
||||
|
||||
MDefinition *obj = walkScopeChain(sc.hops);
|
||||
|
||||
RootedShape shape(cx, ScopeCoordinateToStaticScopeShape(cx, script(), pc));
|
||||
@ -8180,34 +8122,6 @@ IonBuilder::jsop_getaliasedvar(ScopeCoordinate sc)
|
||||
bool
|
||||
IonBuilder::jsop_setaliasedvar(ScopeCoordinate sc)
|
||||
{
|
||||
RootedObject call(cx);
|
||||
if (hasStaticScopeObject(sc, &call)) {
|
||||
uint32_t depth = current->stackDepth() + 1;
|
||||
if (depth > current->nslots()) {
|
||||
if (!current->increaseSlots(depth - current->nslots()))
|
||||
return false;
|
||||
}
|
||||
MDefinition *value = current->pop();
|
||||
RootedPropertyName name(cx, ScopeCoordinateName(cx, script(), pc));
|
||||
|
||||
if (call) {
|
||||
// Push the object on the stack to match the bound object expected in
|
||||
// the global and property set cases.
|
||||
MInstruction *constant = MConstant::New(ObjectValue(*call));
|
||||
current->add(constant);
|
||||
current->push(constant);
|
||||
current->push(value);
|
||||
return setStaticName(call, name);
|
||||
}
|
||||
|
||||
// The call object has type information we need to respect but we
|
||||
// couldn't find it. Just do a normal property assign.
|
||||
MDefinition *obj = walkScopeChain(sc.hops);
|
||||
current->push(obj);
|
||||
current->push(value);
|
||||
return jsop_setprop(name);
|
||||
}
|
||||
|
||||
MDefinition *rval = current->peek(-1);
|
||||
MDefinition *obj = walkScopeChain(sc.hops);
|
||||
|
||||
|
@ -339,11 +339,9 @@ class IonBuilder : public MIRGenerator
|
||||
|
||||
bool invalidatedIdempotentCache();
|
||||
|
||||
bool hasStaticScopeObject(ScopeCoordinate sc, MutableHandleObject pcall);
|
||||
bool loadSlot(MDefinition *obj, Shape *shape, MIRType rvalType,
|
||||
bool barrier, types::StackTypeSet *types);
|
||||
bool storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool needsBarrier,
|
||||
MIRType slotType = MIRType_None);
|
||||
bool storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool needsBarrier);
|
||||
|
||||
// jsop_getprop() helpers.
|
||||
bool getPropTryArgumentsLength(bool *emitted);
|
||||
@ -383,8 +381,8 @@ class IonBuilder : public MIRGenerator
|
||||
bool jsop_dup2();
|
||||
bool jsop_loophead(jsbytecode *pc);
|
||||
bool jsop_compare(JSOp op);
|
||||
bool getStaticName(HandleObject staticObject, HandlePropertyName name);
|
||||
bool setStaticName(HandleObject staticObject, HandlePropertyName name);
|
||||
bool jsop_getgname(HandlePropertyName name);
|
||||
bool jsop_setgname(HandlePropertyName name);
|
||||
bool jsop_getname(HandlePropertyName name);
|
||||
bool jsop_intrinsic(HandlePropertyName name);
|
||||
bool jsop_bindname(PropertyName *name);
|
||||
@ -408,7 +406,6 @@ class IonBuilder : public MIRGenerator
|
||||
bool jsop_arguments_length();
|
||||
bool jsop_arguments_getelem();
|
||||
bool jsop_arguments_setelem(MDefinition *object, MDefinition *index, MDefinition *value);
|
||||
bool jsop_runonce();
|
||||
bool jsop_rest();
|
||||
bool jsop_not();
|
||||
bool jsop_getprop(HandlePropertyName name);
|
||||
|
@ -4106,16 +4106,6 @@ class LGetArgument : public LInstructionHelper<BOX_PIECES, 1, 0>
|
||||
}
|
||||
};
|
||||
|
||||
class LRunOncePrologue : public LCallInstructionHelper<0, 0, 0>
|
||||
{
|
||||
public:
|
||||
LIR_HEADER(RunOncePrologue)
|
||||
|
||||
MRunOncePrologue *mir() const {
|
||||
return mir_->toRunOncePrologue();
|
||||
}
|
||||
};
|
||||
|
||||
// Create the rest parameter.
|
||||
class LRest : public LCallInstructionHelper<1, 1, 3>
|
||||
{
|
||||
@ -4161,6 +4151,7 @@ class LParRest : public LCallInstructionHelper<1, 2, 3>
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class LParWriteGuard : public LCallInstructionHelper<0, 2, 1>
|
||||
{
|
||||
public:
|
||||
|
@ -205,7 +205,6 @@
|
||||
_(StringLength) \
|
||||
_(ArgumentsLength) \
|
||||
_(GetArgument) \
|
||||
_(RunOncePrologue) \
|
||||
_(Rest) \
|
||||
_(ParRest) \
|
||||
_(TypeOfV) \
|
||||
|
@ -2472,13 +2472,6 @@ LIRGenerator::visitGetArgument(MGetArgument *ins)
|
||||
return defineBox(lir, ins);
|
||||
}
|
||||
|
||||
bool
|
||||
LIRGenerator::visitRunOncePrologue(MRunOncePrologue *ins)
|
||||
{
|
||||
LRunOncePrologue *lir = new LRunOncePrologue;
|
||||
return add(lir, ins) && assignSafepoint(lir, ins);
|
||||
}
|
||||
|
||||
bool
|
||||
LIRGenerator::visitRest(MRest *ins)
|
||||
{
|
||||
|
@ -219,7 +219,6 @@ class LIRGenerator : public LIRGeneratorSpecific
|
||||
bool visitStringLength(MStringLength *ins);
|
||||
bool visitArgumentsLength(MArgumentsLength *ins);
|
||||
bool visitGetArgument(MGetArgument *ins);
|
||||
bool visitRunOncePrologue(MRunOncePrologue *ins);
|
||||
bool visitRest(MRest *ins);
|
||||
bool visitParRest(MParRest *ins);
|
||||
bool visitThrow(MThrow *ins);
|
||||
|
@ -2273,23 +2273,6 @@ class MSetArgumentsObjectArg
|
||||
}
|
||||
};
|
||||
|
||||
class MRunOncePrologue
|
||||
: public MNullaryInstruction
|
||||
{
|
||||
protected:
|
||||
MRunOncePrologue()
|
||||
{
|
||||
setGuard();
|
||||
}
|
||||
|
||||
public:
|
||||
INSTRUCTION_HEADER(RunOncePrologue)
|
||||
|
||||
static MRunOncePrologue *New() {
|
||||
return new MRunOncePrologue();
|
||||
}
|
||||
};
|
||||
|
||||
// Given a MIRType_Value A and a MIRType_Object B:
|
||||
// If the Value may be safely unboxed to an Object, return Object(A).
|
||||
// Otherwise, return B.
|
||||
@ -7367,12 +7350,10 @@ class MNewDeclEnvObject : public MNullaryInstruction
|
||||
class MNewCallObject : public MUnaryInstruction
|
||||
{
|
||||
CompilerRootObject templateObj_;
|
||||
bool needsSingletonType_;
|
||||
|
||||
MNewCallObject(HandleObject templateObj, bool needsSingletonType, MDefinition *slots)
|
||||
MNewCallObject(HandleObject templateObj, MDefinition *slots)
|
||||
: MUnaryInstruction(slots),
|
||||
templateObj_(templateObj),
|
||||
needsSingletonType_(needsSingletonType)
|
||||
templateObj_(templateObj)
|
||||
{
|
||||
setResultType(MIRType_Object);
|
||||
}
|
||||
@ -7380,8 +7361,8 @@ class MNewCallObject : public MUnaryInstruction
|
||||
public:
|
||||
INSTRUCTION_HEADER(NewCallObject)
|
||||
|
||||
static MNewCallObject *New(HandleObject templateObj, bool needsSingletonType, MDefinition *slots) {
|
||||
return new MNewCallObject(templateObj, needsSingletonType, slots);
|
||||
static MNewCallObject *New(HandleObject templateObj, MDefinition *slots) {
|
||||
return new MNewCallObject(templateObj, slots);
|
||||
}
|
||||
|
||||
MDefinition *slots() {
|
||||
@ -7390,9 +7371,6 @@ class MNewCallObject : public MUnaryInstruction
|
||||
JSObject *templateObject() {
|
||||
return templateObj_;
|
||||
}
|
||||
bool needsSingletonType() {
|
||||
return needsSingletonType_;
|
||||
}
|
||||
AliasSet getAliasSet() const {
|
||||
return AliasSet::None();
|
||||
}
|
||||
|
@ -154,7 +154,6 @@ namespace ion {
|
||||
_(StringLength) \
|
||||
_(ArgumentsLength) \
|
||||
_(GetArgument) \
|
||||
_(RunOncePrologue) \
|
||||
_(Rest) \
|
||||
_(Floor) \
|
||||
_(Round) \
|
||||
|
@ -239,7 +239,6 @@ class ParallelArrayVisitor : public MInstructionVisitor
|
||||
SAFE_OP(StringLength)
|
||||
UNSAFE_OP(ArgumentsLength)
|
||||
UNSAFE_OP(GetArgument)
|
||||
UNSAFE_OP(RunOncePrologue)
|
||||
CUSTOM_OP(Rest)
|
||||
SAFE_OP(ParRest)
|
||||
SAFE_OP(Floor)
|
||||
|
@ -388,22 +388,13 @@ StringFromCharCode(JSContext *cx, int32_t code)
|
||||
|
||||
bool
|
||||
SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value,
|
||||
bool strict, int jsop)
|
||||
bool strict, bool isSetName)
|
||||
{
|
||||
RootedValue v(cx, value);
|
||||
RootedId id(cx, NameToId(name));
|
||||
|
||||
if (jsop == JSOP_SETALIASEDVAR) {
|
||||
// Aliased var assigns ignore readonly attributes on the property, as
|
||||
// required for initializing 'const' closure variables.
|
||||
Shape *shape = obj->nativeLookup(cx, name);
|
||||
JS_ASSERT(shape && shape->hasSlot());
|
||||
JSObject::nativeSetSlotWithType(cx, obj, shape, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (JS_LIKELY(!obj->getOps()->setProperty)) {
|
||||
unsigned defineHow = (jsop == JSOP_SETNAME || jsop == JSOP_SETGNAME) ? DNP_UNQUALIFIED : 0;
|
||||
unsigned defineHow = isSetName ? DNP_UNQUALIFIED : 0;
|
||||
return baseops::SetPropertyHelper(cx, obj, obj, id, defineHow, &v, strict);
|
||||
}
|
||||
|
||||
@ -434,10 +425,9 @@ NewSlots(JSRuntime *rt, unsigned nslots)
|
||||
}
|
||||
|
||||
JSObject *
|
||||
NewCallObject(JSContext *cx, HandleScript script,
|
||||
HandleShape shape, HandleTypeObject type, HeapSlot *slots)
|
||||
NewCallObject(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots)
|
||||
{
|
||||
return CallObject::create(cx, script, shape, type, slots);
|
||||
return CallObject::create(cx, shape, type, slots);
|
||||
}
|
||||
|
||||
JSObject *
|
||||
|
@ -510,13 +510,12 @@ bool CharCodeAt(JSContext *cx, HandleString str, int32_t index, uint32_t *code);
|
||||
JSFlatString *StringFromCharCode(JSContext *cx, int32_t code);
|
||||
|
||||
bool SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value,
|
||||
bool strict, int jsop);
|
||||
bool strict, bool isSetName);
|
||||
|
||||
bool InterruptCheck(JSContext *cx);
|
||||
|
||||
HeapSlot *NewSlots(JSRuntime *rt, unsigned nslots);
|
||||
JSObject *NewCallObject(JSContext *cx, HandleScript script,
|
||||
HandleShape shape, HandleTypeObject type, HeapSlot *slots);
|
||||
JSObject *NewCallObject(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots);
|
||||
JSObject *NewStringObject(JSContext *cx, HandleString str);
|
||||
|
||||
bool SPSEnter(JSContext *cx, HandleScript script);
|
||||
|
@ -1,8 +0,0 @@
|
||||
assertEq((function() {
|
||||
for (var i = 0; i < 5; i++) {
|
||||
print(i);
|
||||
}
|
||||
const x = 3;
|
||||
(function() x++)();
|
||||
return x
|
||||
})(), 3);
|
@ -1,28 +0,0 @@
|
||||
// Test that closures are deoptimized if they unexpectedly run multiple times.
|
||||
|
||||
with({}){}
|
||||
|
||||
(function(n) {
|
||||
if (n == 20) {
|
||||
outer = (function f() { return f.caller; })();
|
||||
inner = function g() { return x++; }
|
||||
}
|
||||
var x = 0;
|
||||
for (var i = 0; i < n; i++)
|
||||
x++;
|
||||
if (n != 20)
|
||||
inner = function g() { return x++; }
|
||||
})(20);
|
||||
|
||||
oldInner = inner;
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
assertEq(oldInner(), 20 + i);
|
||||
|
||||
outer(40);
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
assertEq(inner(), 40 + i);
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
assertEq(oldInner(), 25 + i);
|
@ -4123,7 +4123,6 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeInferen
|
||||
case JSOP_TABLESWITCH:
|
||||
case JSOP_TRY:
|
||||
case JSOP_LABEL:
|
||||
case JSOP_RUNONCE:
|
||||
break;
|
||||
|
||||
/* Bytecodes pushing values of known type. */
|
||||
|
@ -395,14 +395,8 @@ enum {
|
||||
/* Whether any objects emulate undefined; see EmulatesUndefined. */
|
||||
OBJECT_FLAG_EMULATES_UNDEFINED = 0x00400000,
|
||||
|
||||
/*
|
||||
* For the function on a run-once script, whether the function has actually
|
||||
* run multiple times.
|
||||
*/
|
||||
OBJECT_FLAG_RUNONCE_INVALIDATED = 0x00800000,
|
||||
|
||||
/* Flags which indicate dynamic properties of represented objects. */
|
||||
OBJECT_FLAG_DYNAMIC_MASK = 0x00ff0000,
|
||||
OBJECT_FLAG_DYNAMIC_MASK = 0x007f0000,
|
||||
|
||||
/*
|
||||
* Whether all properties of this object are considered unknown.
|
||||
|
@ -13,8 +13,6 @@
|
||||
#include "jscompartment.h"
|
||||
#include "jsinfer.h"
|
||||
#include "jsprf.h"
|
||||
#include "jsproxy.h"
|
||||
#include "jstypedarray.h"
|
||||
|
||||
#include "builtin/ParallelArray.h"
|
||||
#ifdef JS_ION
|
||||
|
@ -1287,6 +1287,7 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode, bool
|
||||
|
||||
/* No-ops for ease of decompilation. */
|
||||
ADD_EMPTY_CASE(JSOP_NOP)
|
||||
ADD_EMPTY_CASE(JSOP_UNUSED71)
|
||||
ADD_EMPTY_CASE(JSOP_UNUSED132)
|
||||
ADD_EMPTY_CASE(JSOP_UNUSED148)
|
||||
ADD_EMPTY_CASE(JSOP_UNUSED161)
|
||||
@ -2520,13 +2521,6 @@ BEGIN_CASE(JSOP_ARGUMENTS)
|
||||
}
|
||||
END_CASE(JSOP_ARGUMENTS)
|
||||
|
||||
BEGIN_CASE(JSOP_RUNONCE)
|
||||
{
|
||||
if (!RunOnceScriptPrologue(cx, script))
|
||||
goto error;
|
||||
}
|
||||
END_CASE(JSOP_RUNONCE)
|
||||
|
||||
BEGIN_CASE(JSOP_REST)
|
||||
{
|
||||
RootedObject &rest = rootObject0;
|
||||
@ -2555,13 +2549,7 @@ END_CASE(JSOP_GETALIASEDVAR)
|
||||
BEGIN_CASE(JSOP_SETALIASEDVAR)
|
||||
{
|
||||
ScopeCoordinate sc = ScopeCoordinate(regs.pc);
|
||||
ScopeObject &obj = regs.fp()->aliasedVarScope(sc);
|
||||
|
||||
// Avoid computing the name if no type updates are needed, as this may be
|
||||
// expensive on scopes with large numbers of variables.
|
||||
PropertyName *name = obj.hasSingletonType() ? ScopeCoordinateName(cx, script, regs.pc) : NULL;
|
||||
|
||||
obj.setAliasedVar(cx, sc, name, regs.sp[-1]);
|
||||
regs.fp()->aliasedVarScope(sc).setAliasedVar(sc, regs.sp[-1]);
|
||||
}
|
||||
END_CASE(JSOP_SETALIASEDVAR)
|
||||
|
||||
@ -3654,22 +3642,3 @@ js::ImplicitThisOperation(JSContext *cx, HandleObject scopeObj, HandlePropertyNa
|
||||
|
||||
return ComputeImplicitThis(cx, obj, res);
|
||||
}
|
||||
|
||||
bool
|
||||
js::RunOnceScriptPrologue(JSContext *cx, HandleScript script)
|
||||
{
|
||||
JS_ASSERT(script->treatAsRunOnce);
|
||||
|
||||
if (!script->hasRunOnce) {
|
||||
script->hasRunOnce = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Force instantiation of the script's function's type to ensure the flag
|
||||
// is preserved in type information.
|
||||
if (!script->function()->getType(cx))
|
||||
return false;
|
||||
|
||||
types::MarkTypeObjectFlags(cx, script->function(), types::OBJECT_FLAG_RUNONCE_INVALIDATED);
|
||||
return true;
|
||||
}
|
||||
|
@ -414,9 +414,6 @@ IteratorMore(JSContext *cx, JSObject *iterobj, bool *cond, MutableHandleValue rv
|
||||
bool
|
||||
IteratorNext(JSContext *cx, HandleObject iterobj, MutableHandleValue rval);
|
||||
|
||||
bool
|
||||
RunOnceScriptPrologue(JSContext *cx, HandleScript script);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* jsinterp_h___ */
|
||||
|
@ -160,12 +160,7 @@ OPDEF(JSOP_AND, 69, "and", NULL, 5, 1, 1, 6, JOF_JUMP|J
|
||||
|
||||
/* The switch bytecodes have variable length. */
|
||||
OPDEF(JSOP_TABLESWITCH, 70, "tableswitch", NULL, -1, 1, 0, 0, JOF_TABLESWITCH|JOF_DETECTING)
|
||||
|
||||
/*
|
||||
* Prologue emitted in scripts expected to run once, which deoptimizes code if
|
||||
* it executes multiple times.
|
||||
*/
|
||||
OPDEF(JSOP_RUNONCE, 71, "runonce", NULL, 1, 0, 0, 0, JOF_BYTE)
|
||||
OPDEF(JSOP_UNUSED71, 71, "unused71", NULL, 1, 0, 0, 0, JOF_BYTE)
|
||||
|
||||
/* New, infallible/transitive identity ops. */
|
||||
OPDEF(JSOP_STRICTEQ, 72, "stricteq", "===", 1, 2, 1, 10, JOF_BYTE|JOF_DETECTING|JOF_LEFTASSOC|JOF_ARITH)
|
||||
|
@ -2776,7 +2776,7 @@ js::SetFrameArgumentsObject(JSContext *cx, AbstractFramePtr frame,
|
||||
JS_ASSERT(*pc == JSOP_SETALIASEDVAR);
|
||||
|
||||
if (frame.callObj().asScope().aliasedVar(pc).isMagic(JS_OPTIMIZED_ARGUMENTS))
|
||||
frame.callObj().asScope().setAliasedVar(cx, pc, cx->names().arguments, ObjectValue(*argsobj));
|
||||
frame.callObj().asScope().setAliasedVar(pc, ObjectValue(*argsobj));
|
||||
} else {
|
||||
if (frame.unaliasedLocal(var).isMagic(JS_OPTIMIZED_ARGUMENTS))
|
||||
frame.unaliasedLocal(var) = ObjectValue(*argsobj);
|
||||
|
@ -411,7 +411,6 @@ class JSScript : public js::gc::Cell
|
||||
script */
|
||||
bool hasSingletons:1; /* script has singleton objects */
|
||||
bool treatAsRunOnce:1; /* script is a lambda to treat as running once. */
|
||||
bool hasRunOnce:1; /* if treatAsRunOnce, whether script has executed. */
|
||||
bool hasBeenCloned:1; /* script has been reused for a clone. */
|
||||
bool isActiveEval:1; /* script came from eval(), and is still active */
|
||||
bool isCachedEval:1; /* script came from eval(), and is in eval cache */
|
||||
|
@ -81,7 +81,7 @@ ArgumentsObject::element(uint32_t i) const
|
||||
}
|
||||
|
||||
inline void
|
||||
ArgumentsObject::setElement(JSContext *cx, uint32_t i, const Value &v)
|
||||
ArgumentsObject::setElement(uint32_t i, const Value &v)
|
||||
{
|
||||
JS_ASSERT(!isElementDeleted(i));
|
||||
HeapValue &lhs = data()->args[i];
|
||||
@ -89,7 +89,7 @@ ArgumentsObject::setElement(JSContext *cx, uint32_t i, const Value &v)
|
||||
CallObject &callobj = getFixedSlot(MAYBE_CALL_SLOT).toObject().asCall();
|
||||
for (AliasedFormalIter fi(callobj.callee().nonLazyScript()); ; fi++) {
|
||||
if (fi.frameIndex() == i) {
|
||||
callobj.setAliasedVar(cx, fi, fi->name(), v);
|
||||
callobj.setAliasedVar(fi, v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -352,7 +352,7 @@ ArgSetter(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, MutableHa
|
||||
if (JSID_IS_INT(id)) {
|
||||
unsigned arg = unsigned(JSID_TO_INT(id));
|
||||
if (arg < argsobj.initialLength() && !argsobj.isElementDeleted(arg)) {
|
||||
argsobj.setElement(cx, arg, vp);
|
||||
argsobj.setElement(arg, vp);
|
||||
if (arg < script->function()->nargs)
|
||||
types::TypeScript::SetArgument(cx, script, arg, vp);
|
||||
return true;
|
||||
@ -475,7 +475,7 @@ StrictArgSetter(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Mut
|
||||
if (JSID_IS_INT(id)) {
|
||||
unsigned arg = unsigned(JSID_TO_INT(id));
|
||||
if (arg < argsobj->initialLength()) {
|
||||
argsobj->setElement(cx, arg, vp);
|
||||
argsobj->setElement(arg, vp);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
|
@ -177,7 +177,7 @@ class ArgumentsObject : public JSObject
|
||||
* needed, the frontend should have emitted JSOP_GETALIASEDVAR.
|
||||
*/
|
||||
inline const Value &element(uint32_t i) const;
|
||||
inline void setElement(JSContext *cx, uint32_t i, const Value &v);
|
||||
inline void setElement(uint32_t i, const Value &v);
|
||||
inline const Value &arg(unsigned i) const;
|
||||
inline void setArg(unsigned i, const Value &v);
|
||||
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
#include "ScopeObject.h"
|
||||
|
||||
#include "jsinferinlines.h"
|
||||
#include "jsscriptinlines.h"
|
||||
|
||||
namespace js {
|
||||
@ -43,17 +42,11 @@ ScopeObject::aliasedVar(ScopeCoordinate sc)
|
||||
}
|
||||
|
||||
inline void
|
||||
ScopeObject::setAliasedVar(JSContext *cx, ScopeCoordinate sc, PropertyName *name, const Value &v)
|
||||
ScopeObject::setAliasedVar(ScopeCoordinate sc, const Value &v)
|
||||
{
|
||||
JS_ASSERT(isCall() || isClonedBlock());
|
||||
JS_STATIC_ASSERT(CallObject::RESERVED_SLOTS == BlockObject::RESERVED_SLOTS);
|
||||
|
||||
// name may be null for non-singletons, whose types do not need to be tracked.
|
||||
JS_ASSERT_IF(hasSingletonType(), name);
|
||||
|
||||
setSlot(sc.slot, v);
|
||||
if (hasSingletonType())
|
||||
types::AddTypePropertyId(cx, this, NameToId(name), v);
|
||||
}
|
||||
|
||||
/*static*/ inline size_t
|
||||
@ -84,12 +77,9 @@ CallObject::aliasedVar(AliasedFormalIter fi)
|
||||
}
|
||||
|
||||
inline void
|
||||
CallObject::setAliasedVar(JSContext *cx, AliasedFormalIter fi, PropertyName *name, const Value &v)
|
||||
CallObject::setAliasedVar(AliasedFormalIter fi, const Value &v)
|
||||
{
|
||||
JS_ASSERT(name == fi->name());
|
||||
setSlot(fi.scopeSlot(), v);
|
||||
if (hasSingletonType())
|
||||
types::AddTypePropertyId(cx, this, NameToId(name), v);
|
||||
}
|
||||
|
||||
/*static*/ inline size_t
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user