merge autoland to mozilla-central. r=merge a=merge
MozReview-Commit-ID: JRsSap6SwOZ
|
@ -124,6 +124,9 @@ RootAccessibleWrap::accNavigate(
|
|||
if (!pvarEndUpAt) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
if (IsDefunct()) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
|
||||
Accessible* target = nullptr;
|
||||
// Get the document in the active tab.
|
||||
|
|
|
@ -34,12 +34,6 @@
|
|||
accesskey="&newPrivateWindow.accesskey;"
|
||||
command="Tools:PrivateBrowsing"
|
||||
key="key_privatebrowsing"/>
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
<menuitem id="menu_newNonRemoteWindow"
|
||||
label="&newNonRemoteWindow.label;"
|
||||
hidden="true"
|
||||
command="Tools:NonRemoteWindow"/>
|
||||
#endif
|
||||
#ifdef MAC_NON_BROWSER_WINDOW
|
||||
<menuitem id="menu_openLocation"
|
||||
label="&openLocationCmd.label;"
|
||||
|
|
|
@ -102,10 +102,6 @@
|
|||
oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
|
||||
<command id="Tools:PrivateBrowsing"
|
||||
oncommand="OpenBrowserWindow({private: true});"/>
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
<command id="Tools:NonRemoteWindow"
|
||||
oncommand="OpenBrowserWindow({remote: false});"/>
|
||||
#endif
|
||||
<command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
|
||||
<command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
|
||||
</commandset>
|
||||
|
|
|
@ -1466,9 +1466,6 @@ var gBrowserInit = {
|
|||
IndexedDBPromptHelper.init();
|
||||
CanvasPermissionPromptHelper.init();
|
||||
|
||||
if (AppConstants.E10S_TESTING_ONLY)
|
||||
gRemoteTabsUI.init();
|
||||
|
||||
// Initialize the full zoom setting.
|
||||
// We do this before the session restore service gets initialized so we can
|
||||
// apply full zoom settings to tabs restored by the session restore service.
|
||||
|
@ -1982,9 +1979,6 @@ if (AppConstants.platform == "macosx") {
|
|||
// initialize the private browsing UI
|
||||
gPrivateBrowsingUI.init();
|
||||
|
||||
if (AppConstants.E10S_TESTING_ONLY) {
|
||||
gRemoteTabsUI.init();
|
||||
}
|
||||
};
|
||||
|
||||
gBrowserInit.nonBrowserWindowShutdown = function() {
|
||||
|
@ -8283,27 +8277,6 @@ var gPrivateBrowsingUI = {
|
|||
}
|
||||
};
|
||||
|
||||
var gRemoteTabsUI = {
|
||||
init() {
|
||||
if (window.location.href != getBrowserURL() &&
|
||||
// Also check hidden window for the Mac no-window case
|
||||
window.location.href != "chrome://browser/content/hiddenWindow.xul") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "macosx" &&
|
||||
Services.prefs.getBoolPref("layers.acceleration.disabled")) {
|
||||
// On OS X, "Disable Hardware Acceleration" also disables OMTC and forces
|
||||
// a fallback to Basic Layers. This is incompatible with e10s.
|
||||
return;
|
||||
}
|
||||
|
||||
let newNonRemoteWindow = document.getElementById("menu_newNonRemoteWindow");
|
||||
let autostart = Services.appinfo.browserTabsRemoteAutostart;
|
||||
newNonRemoteWindow.hidden = !autostart;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Switch to a tab that has a given URI, and focuses its browser window.
|
||||
* If a matching tab is in this window, it will be switched to. Otherwise, other
|
||||
|
@ -8561,16 +8534,6 @@ var TabContextMenu = {
|
|||
if (this.contextTab.hasAttribute("customizemode"))
|
||||
document.getElementById("context_openTabInWindow").disabled = true;
|
||||
|
||||
if (AppConstants.E10S_TESTING_ONLY) {
|
||||
menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-remote");
|
||||
for (let menuItem of menuItems) {
|
||||
menuItem.hidden = !gMultiProcessBrowser;
|
||||
if (menuItem.id == "context_openNonRemoteWindow") {
|
||||
menuItem.disabled = !!parseInt(this.contextTab.getAttribute("usercontextid"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disabled = gBrowser.visibleTabs.length == 1;
|
||||
menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
|
||||
for (let menuItem of menuItems)
|
||||
|
|
|
@ -96,12 +96,6 @@
|
|||
accesskey="&moveToNewWindow.accesskey;"
|
||||
tbattr="tabbrowser-multiple"
|
||||
oncommand="gBrowser.replaceTabWithWindow(TabContextMenu.contextTab);"/>
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
<menuitem id="context_openNonRemoteWindow" label="Open in new non-e10s window"
|
||||
tbattr="tabbrowser-remote"
|
||||
hidden="true"
|
||||
oncommand="gBrowser.openNonRemoteWindow(TabContextMenu.contextTab);"/>
|
||||
#endif
|
||||
<menuseparator id="context_sendTabToDevice_separator"/>
|
||||
<menu id="context_sendTabToDevice" label="&sendTabToDevice.label;"
|
||||
accesskey="&sendTabToDevice.accesskey;">
|
||||
|
|
|
@ -3954,20 +3954,6 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Opens a given tab to a non-remote window. -->
|
||||
<method name="openNonRemoteWindow">
|
||||
<parameter name="aTab"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!AppConstants.E10S_TESTING_ONLY) {
|
||||
throw "This method is intended only for e10s testing!";
|
||||
}
|
||||
let url = aTab.linkedBrowser.currentURI.spec;
|
||||
return window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no,non-remote", url);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="moveTabTo">
|
||||
<parameter name="aTab"/>
|
||||
<parameter name="aIndex"/>
|
||||
|
@ -5544,14 +5530,11 @@
|
|||
}
|
||||
} else {
|
||||
label = tab._fullLabel || tab.getAttribute("label");
|
||||
if (AppConstants.E10S_TESTING_ONLY &&
|
||||
if (AppConstants.NIGHTLY_BUILD &&
|
||||
tab.linkedBrowser &&
|
||||
tab.linkedBrowser.isRemoteBrowser) {
|
||||
label += " - e10s";
|
||||
if (tab.linkedBrowser.frameLoader &&
|
||||
Services.appinfo.maxWebProcessCount > 1) {
|
||||
label += " (" + tab.linkedBrowser.frameLoader.tabParent.osPid + ")";
|
||||
}
|
||||
tab.linkedBrowser.isRemoteBrowser &&
|
||||
tab.linkedBrowser.frameLoader) {
|
||||
label += " (pid " + tab.linkedBrowser.frameLoader.tabParent.osPid + ")";
|
||||
}
|
||||
if (tab.userContextId) {
|
||||
label = this.mStringBundle.getFormattedString("tabs.containers.tooltip", [label, ContextualIdentityService.getUserContextLabel(tab.userContextId)]);
|
||||
|
|
Двоичные данные
browser/branding/aurora/bgstub.bmp
До Ширина: | Высота: | Размер: 809 KiB |
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 48 KiB |
|
@ -19,7 +19,8 @@ def FirefoxBranding():
|
|||
'VisualElements_70.png',
|
||||
]
|
||||
BRANDING_FILES += [
|
||||
'bgstub.bmp',
|
||||
'bgstub.jpg',
|
||||
'bgstub_2x.jpg',
|
||||
'branding.nsi',
|
||||
'document.ico',
|
||||
'firefox.ico',
|
||||
|
|
Двоичные данные
browser/branding/nightly/bgstub.bmp
До Ширина: | Высота: | Размер: 809 KiB |
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 49 KiB |
Двоичные данные
browser/branding/official/bgstub.bmp
До Ширина: | Высота: | Размер: 809 KiB |
После Ширина: | Высота: | Размер: 18 KiB |
После Ширина: | Высота: | Размер: 88 KiB |
Двоичные данные
browser/branding/unofficial/bgstub.bmp
До Ширина: | Высота: | Размер: 809 KiB |
После Ширина: | Высота: | Размер: 17 KiB |
После Ширина: | Высота: | Размер: 52 KiB |
|
@ -59,7 +59,7 @@ const kSubviewEvents = [
|
|||
* The current version. We can use this to auto-add new default widgets as necessary.
|
||||
* (would be const but isn't because of testing purposes)
|
||||
*/
|
||||
var kVersion = 12;
|
||||
var kVersion = 13;
|
||||
|
||||
/**
|
||||
* Buttons removed from built-ins by version they were removed. kVersion must be
|
||||
|
@ -345,7 +345,6 @@ var CustomizableUIInternal = {
|
|||
"preferences-button",
|
||||
"add-ons-button",
|
||||
"sync-button",
|
||||
"e10s-button",
|
||||
];
|
||||
|
||||
if (!AppConstants.MOZ_DEV_EDITION) {
|
||||
|
@ -452,6 +451,17 @@ var CustomizableUIInternal = {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old placements from the now-gone Nightly-only
|
||||
// "New non-e10s window" button.
|
||||
if (currentVersion < 13 && gSavedState.placements) {
|
||||
for (let placements of Object.values(gSavedState.placements)) {
|
||||
let buttonIndex = placements.indexOf("e10s-button");
|
||||
if (buttonIndex != -1) {
|
||||
placements.spice(buttonIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -997,21 +997,3 @@ if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (AppConstants.E10S_TESTING_ONLY) {
|
||||
if (Services.appinfo.browserTabsRemoteAutostart) {
|
||||
CustomizableWidgets.push({
|
||||
id: "e10s-button",
|
||||
defaultArea: CustomizableUI.AREA_PANEL,
|
||||
onBuild(aDocument) {
|
||||
let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
|
||||
node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
|
||||
node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
|
||||
},
|
||||
onCommand(aEvent) {
|
||||
let win = aEvent.view;
|
||||
win.OpenBrowserWindow({remote: false});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -587,6 +587,7 @@ CustomizeMode.prototype = {
|
|||
(aNode.id == "downloads-button" && aNode.hidden)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let animationNode;
|
||||
if (aNode.parentNode && aNode.parentNode.id.startsWith("wrapper-")) {
|
||||
animationNode = aNode.parentNode;
|
||||
|
@ -594,13 +595,25 @@ CustomizeMode.prototype = {
|
|||
animationNode = aNode;
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
animationNode.classList.add("animate-out");
|
||||
animationNode.addEventListener("animationend", function cleanupWidgetAnimationEnd(e) {
|
||||
function cleanupCustomizationExit() {
|
||||
resolveAnimationPromise();
|
||||
}
|
||||
|
||||
function cleanupWidgetAnimationEnd(e) {
|
||||
if (e.animationName == "widget-animate-out" && e.target.id == animationNode.id) {
|
||||
resolveAnimationPromise();
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAnimationPromise() {
|
||||
animationNode.removeEventListener("animationend", cleanupWidgetAnimationEnd);
|
||||
animationNode.removeEventListener("customizationending", cleanupCustomizationExit);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
animationNode.classList.add("animate-out");
|
||||
animationNode.ownerGlobal.gNavToolbox.addEventListener("customizationending", cleanupCustomizationExit);
|
||||
animationNode.addEventListener("animationend", cleanupWidgetAnimationEnd);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -17,11 +17,6 @@ Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/
|
|||
Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
|
||||
registerCleanupFunction(() => Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck"));
|
||||
|
||||
// Remove temporary e10s related new window options in customize ui,
|
||||
// they break a lot of tests.
|
||||
CustomizableUI.destroyWidget("e10s-button");
|
||||
CustomizableUI.removeWidgetFromArea("e10s-button");
|
||||
|
||||
var {synthesizeDragStart, synthesizeDrop} = EventUtils;
|
||||
|
||||
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
|
|
@ -97,11 +97,6 @@ const APP_ICON_ATTR_NAME = "appHandlerIcon";
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
|
||||
if (AppConstants.E10S_TESTING_ONLY) {
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
}
|
||||
|
||||
if (AppConstants.MOZ_DEV_EDITION) {
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
|
||||
"resource://gre/modules/FxAccounts.jsm");
|
||||
|
@ -319,26 +314,6 @@ var gMainPane = {
|
|||
}
|
||||
}
|
||||
|
||||
if (AppConstants.E10S_TESTING_ONLY) {
|
||||
setEventListener("e10sAutoStart", "command",
|
||||
gMainPane.enableE10SChange);
|
||||
let e10sCheckbox = document.getElementById("e10sAutoStart");
|
||||
|
||||
let e10sPref = document.getElementById("browser.tabs.remote.autostart");
|
||||
let e10sTempPref = document.getElementById("e10sTempPref");
|
||||
let e10sForceEnable = document.getElementById("e10sForceEnable");
|
||||
|
||||
let preffedOn = e10sPref.value || e10sTempPref.value || e10sForceEnable.value;
|
||||
|
||||
if (preffedOn) {
|
||||
// The checkbox is checked if e10s is preffed on and enabled.
|
||||
e10sCheckbox.checked = Services.appinfo.browserTabsRemoteAutostart;
|
||||
|
||||
// but if it's force disabled, then the checkbox is disabled.
|
||||
e10sCheckbox.disabled = !Services.appinfo.browserTabsRemoteAutostart;
|
||||
}
|
||||
}
|
||||
|
||||
if (AppConstants.MOZ_DEV_EDITION) {
|
||||
let uAppData = OS.Constants.Path.userApplicationDataDir;
|
||||
let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
|
||||
|
@ -546,54 +521,6 @@ var gMainPane = {
|
|||
this.readBrowserContainersCheckbox();
|
||||
},
|
||||
|
||||
isE10SEnabled() {
|
||||
let e10sEnabled;
|
||||
try {
|
||||
let e10sStatus = Components.classes["@mozilla.org/supports-PRUint64;1"]
|
||||
.createInstance(Ci.nsISupportsPRUint64);
|
||||
let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver);
|
||||
appinfo.observe(e10sStatus, "getE10SBlocked", "");
|
||||
e10sEnabled = e10sStatus.data < 2;
|
||||
} catch (e) {
|
||||
e10sEnabled = false;
|
||||
}
|
||||
|
||||
return e10sEnabled;
|
||||
},
|
||||
|
||||
enableE10SChange() {
|
||||
if (AppConstants.E10S_TESTING_ONLY) {
|
||||
let e10sCheckbox = document.getElementById("e10sAutoStart");
|
||||
let e10sPref = document.getElementById("browser.tabs.remote.autostart");
|
||||
let e10sTempPref = document.getElementById("e10sTempPref");
|
||||
|
||||
let prefsToChange;
|
||||
if (e10sCheckbox.checked) {
|
||||
// Enabling e10s autostart
|
||||
prefsToChange = [e10sPref];
|
||||
} else {
|
||||
// Disabling e10s autostart
|
||||
prefsToChange = [e10sPref];
|
||||
if (e10sTempPref.value) {
|
||||
prefsToChange.push(e10sTempPref);
|
||||
}
|
||||
}
|
||||
|
||||
let buttonIndex = confirmRestartPrompt(e10sCheckbox.checked, 0,
|
||||
true, false);
|
||||
if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
|
||||
for (let prefToChange of prefsToChange) {
|
||||
prefToChange.value = e10sCheckbox.checked;
|
||||
}
|
||||
|
||||
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
|
||||
}
|
||||
|
||||
// Revert the checkbox in case we didn't quit
|
||||
e10sCheckbox.checked = e10sPref.value || e10sTempPref.value;
|
||||
}
|
||||
},
|
||||
|
||||
separateProfileModeChange() {
|
||||
if (AppConstants.MOZ_DEV_EDITION) {
|
||||
function quitApp() {
|
||||
|
@ -691,15 +618,24 @@ var gMainPane = {
|
|||
// Set the "Use Current Page(s)" button's text and enabled state.
|
||||
this._updateUseCurrentButton();
|
||||
|
||||
// This is an async task.
|
||||
handleControllingExtension("prefs", "homepage_override")
|
||||
.then((isControlled) => {
|
||||
function setInputDisabledStates(isControlled) {
|
||||
// Disable or enable the inputs based on if this is controlled by an extension.
|
||||
document.querySelectorAll("#browserHomePage, .homepage-button")
|
||||
.forEach((button) => {
|
||||
button.disabled = isControlled;
|
||||
});
|
||||
.forEach((element) => {
|
||||
let isLocked = document.getElementById(element.getAttribute("preference")).locked;
|
||||
element.disabled = isLocked || isControlled;
|
||||
});
|
||||
}
|
||||
|
||||
if (homePref.locked) {
|
||||
// An extension can't control these settings if they're locked.
|
||||
hideControllingExtension("homepage_override");
|
||||
setInputDisabledStates(false);
|
||||
} else {
|
||||
// Asynchronously update the extension controlled UI.
|
||||
handleControllingExtension("prefs", "homepage_override")
|
||||
.then(setInputDisabledStates);
|
||||
}
|
||||
|
||||
// If the pref is set to about:home or about:newtab, set the value to ""
|
||||
// to show the placeholder text (about:home title) rather than
|
||||
|
@ -1276,7 +1212,7 @@ var gMainPane = {
|
|||
},
|
||||
|
||||
buildContentProcessCountMenuList() {
|
||||
if (gMainPane.isE10SEnabled()) {
|
||||
if (Services.appinfo.browserTabsRemoteAutostart) {
|
||||
let processCountPref = document.getElementById("dom.ipc.processCount");
|
||||
let e10sRolloutProcessCountPref =
|
||||
document.getElementById("dom.ipc.processCount.web");
|
||||
|
|
|
@ -17,19 +17,6 @@
|
|||
<stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences.properties"/>
|
||||
|
||||
<preferences id="mainPreferences" hidden="true" data-category="paneGeneral">
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
<preference id="browser.tabs.remote.autostart"
|
||||
name="browser.tabs.remote.autostart"
|
||||
type="bool"/>
|
||||
<preference id="e10sTempPref"
|
||||
name="browser.tabs.remote.autostart.2"
|
||||
type="bool"/>
|
||||
<preference id="e10sForceEnable"
|
||||
name="browser.tabs.remote.force-enable"
|
||||
type="bool"/>
|
||||
#endif
|
||||
|
||||
<!-- Startup -->
|
||||
<preference id="browser.startup.page"
|
||||
name="browser.startup.page"
|
||||
|
@ -298,11 +285,6 @@
|
|||
</vbox>
|
||||
#endif
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
<checkbox id="e10sAutoStart"
|
||||
label="&e10sEnabled.label;"/>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SHELL_SERVICE
|
||||
<vbox id="defaultBrowserBox">
|
||||
<checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
|
||||
|
|
|
@ -32,19 +32,24 @@ function installAddon(xpiName) {
|
|||
});
|
||||
}
|
||||
|
||||
function waitForMessageChange(messageId, cb) {
|
||||
function waitForMutation(target, opts, cb) {
|
||||
return new Promise((resolve) => {
|
||||
let target = gBrowser.contentDocument.getElementById(messageId);
|
||||
let observer = new MutationObserver(() => {
|
||||
if (cb(target)) {
|
||||
if (!cb || cb(target)) {
|
||||
observer.disconnect();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
observer.observe(target, { attributes: true, attributeFilter: ["hidden"] });
|
||||
observer.observe(target, opts);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForMessageChange(messageId, cb) {
|
||||
return waitForMutation(
|
||||
gBrowser.contentDocument.getElementById(messageId),
|
||||
{ attributes: true, attributeFilter: ["hidden"] }, cb);
|
||||
}
|
||||
|
||||
function waitForMessageHidden(messageId) {
|
||||
return waitForMessageChange(messageId, target => target.hidden);
|
||||
}
|
||||
|
@ -90,6 +95,146 @@ add_task(async function testExtensionControlledHomepage() {
|
|||
is(doc.getElementById("browserHomePage").disabled, false, "The homepage input is enabled");
|
||||
is(controlledContent.hidden, true, "The extension controlled row is hidden");
|
||||
|
||||
let addon = await AddonManager.getAddonByID("@set_homepage");
|
||||
// Enable the extension so we get the UNINSTALL event, which is needed by
|
||||
// ExtensionPreferencesManager to clean up properly.
|
||||
// FIXME: See https://bugzilla.mozilla.org/show_bug.cgi?id=1408226.
|
||||
addon.userDisabled = false;
|
||||
await waitForMessageShown("browserHomePageExtensionContent");
|
||||
// Do the uninstall now that the enable code has been run.
|
||||
addon.uninstall();
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
||||
add_task(async function testPrefLockedHomepage() {
|
||||
await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
|
||||
let doc = gBrowser.contentDocument;
|
||||
is(gBrowser.currentURI.spec, "about:preferences#general",
|
||||
"#general should be in the URI for about:preferences");
|
||||
|
||||
let homePagePref = "browser.startup.homepage";
|
||||
let buttonPrefs = [
|
||||
"pref.browser.homepage.disable_button.current_page",
|
||||
"pref.browser.homepage.disable_button.bookmark_page",
|
||||
"pref.browser.homepage.disable_button.restore_default",
|
||||
];
|
||||
let homePageInput = doc.getElementById("browserHomePage");
|
||||
let prefs = Services.prefs.getDefaultBranch(null);
|
||||
let mutationOpts = {attributes: true, attributeFilter: ["disabled"]};
|
||||
let controlledContent = doc.getElementById("browserHomePageExtensionContent");
|
||||
|
||||
// Helper functions.
|
||||
let getButton = pref => doc.querySelector(`.homepage-button[preference="${pref}"`);
|
||||
let waitForAllMutations = () => Promise.all(
|
||||
buttonPrefs.map(pref => waitForMutation(getButton(pref), mutationOpts))
|
||||
.concat([waitForMutation(homePageInput, mutationOpts)]));
|
||||
let getHomepage = () => Services.prefs.getCharPref("browser.startup.homepage");
|
||||
|
||||
let originalHomepage = getHomepage();
|
||||
let extensionHomepage = "https://developer.mozilla.org/";
|
||||
let lockedHomepage = "http://www.yahoo.com";
|
||||
|
||||
let lockPrefs = () => {
|
||||
buttonPrefs.forEach(pref => {
|
||||
prefs.setBoolPref(pref, true);
|
||||
prefs.lockPref(pref);
|
||||
});
|
||||
// Do the homepage last since that's the only pref that triggers a UI update.
|
||||
prefs.setCharPref(homePagePref, lockedHomepage);
|
||||
prefs.lockPref(homePagePref);
|
||||
};
|
||||
let unlockPrefs = () => {
|
||||
buttonPrefs.forEach(pref => {
|
||||
prefs.unlockPref(pref);
|
||||
prefs.setBoolPref(pref, false);
|
||||
});
|
||||
// Do the homepage last since that's the only pref that triggers a UI update.
|
||||
prefs.unlockPref(homePagePref);
|
||||
prefs.setCharPref(homePagePref, originalHomepage);
|
||||
};
|
||||
|
||||
ok(originalHomepage != extensionHomepage, "The extension will change the homepage");
|
||||
|
||||
// Install an extension that sets the homepage to MDN.
|
||||
await installAddon("set_homepage.xpi");
|
||||
await waitForMessageShown(controlledContent.id);
|
||||
|
||||
// Check that everything is still disabled, homepage didn't change.
|
||||
is(getHomepage(), extensionHomepage, "The reported homepage is set by the extension");
|
||||
is(homePageInput.value, extensionHomepage, "The homepage is set by the extension");
|
||||
is(homePageInput.disabled, true, "Homepage is disabled when set by extension");
|
||||
buttonPrefs.forEach(pref => {
|
||||
is(getButton(pref).disabled, true, `${pref} is disabled when set by extension`);
|
||||
});
|
||||
is(controlledContent.hidden, false, "The extension controlled message is shown");
|
||||
|
||||
// Lock all of the prefs, wait for the UI to update.
|
||||
let messageHidden = waitForMessageHidden(controlledContent.id);
|
||||
lockPrefs();
|
||||
await messageHidden;
|
||||
|
||||
// Check that everything is now disabled.
|
||||
is(getHomepage(), lockedHomepage, "The reported homepage is set by the pref");
|
||||
is(homePageInput.value, lockedHomepage, "The homepage is set by the pref");
|
||||
is(homePageInput.disabled, true, "The homepage is disabed when the pref is locked");
|
||||
buttonPrefs.forEach(pref => {
|
||||
is(getButton(pref).disabled, true, `The ${pref} button is disabled when locked`);
|
||||
});
|
||||
is(controlledContent.hidden, true, "The extension controlled message is hidden when locked");
|
||||
|
||||
// Unlock the prefs, wait for the UI to update.
|
||||
let messageShown = waitForMessageShown(controlledContent.id);
|
||||
unlockPrefs();
|
||||
await messageShown;
|
||||
|
||||
// Verify that the UI is showing the extension's settings.
|
||||
is(homePageInput.value, extensionHomepage, "The homepage is set by the extension");
|
||||
is(homePageInput.disabled, true, "Homepage is disabled when set by extension");
|
||||
buttonPrefs.forEach(pref => {
|
||||
is(getButton(pref).disabled, true, `${pref} is disabled when set by extension`);
|
||||
});
|
||||
is(controlledContent.hidden, false, "The extension controlled message is shown when unlocked");
|
||||
|
||||
// Uninstall the add-on.
|
||||
let addon = await AddonManager.getAddonByID("@set_homepage");
|
||||
addon.uninstall();
|
||||
await waitForMessageHidden(controlledContent.id);
|
||||
|
||||
// Check that everything is now enabled again.
|
||||
is(getHomepage(), originalHomepage, "The reported homepage is reset to original value");
|
||||
is(homePageInput.value, "", "The homepage is empty");
|
||||
is(homePageInput.disabled, false, "The homepage is enabled after clearing lock");
|
||||
buttonPrefs.forEach(pref => {
|
||||
is(getButton(pref).disabled, false, `The ${pref} button is enabled when unlocked`);
|
||||
});
|
||||
|
||||
// Lock the prefs without an extension.
|
||||
lockPrefs();
|
||||
await waitForAllMutations();
|
||||
|
||||
// Check that everything is now disabled.
|
||||
is(getHomepage(), lockedHomepage, "The reported homepage is set by the pref");
|
||||
is(homePageInput.value, lockedHomepage, "The homepage is set by the pref");
|
||||
is(homePageInput.disabled, true, "The homepage is disabed when the pref is locked");
|
||||
buttonPrefs.forEach(pref => {
|
||||
is(getButton(pref).disabled, true, `The ${pref} button is disabled when locked`);
|
||||
});
|
||||
|
||||
// Unlock the prefs without an extension.
|
||||
unlockPrefs();
|
||||
await waitForAllMutations();
|
||||
|
||||
// Check that everything is enabled again.
|
||||
is(getHomepage(), originalHomepage, "The homepage is reset to the original value");
|
||||
is(homePageInput.value, "", "The homepage is clear after being unlocked");
|
||||
is(homePageInput.disabled, false, "The homepage is enabled after clearing lock");
|
||||
buttonPrefs.forEach(pref => {
|
||||
is(getButton(pref).disabled, false, `The ${pref} button is enabled when unlocked`);
|
||||
});
|
||||
is(controlledContent.hidden, true,
|
||||
"The extension controlled message is hidden when unlocked with no extension");
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ class FieldScanner {
|
|||
* The latest index of elements waiting for parsing.
|
||||
*/
|
||||
set parsingIndex(index) {
|
||||
if (index > this.fieldDetails.length) {
|
||||
if (index > this._elements.length) {
|
||||
throw new Error("The parsing index is out of range.");
|
||||
}
|
||||
this._parsingIndex = index;
|
||||
|
@ -288,6 +288,70 @@ this.LabelUtils = {
|
|||
this.FormAutofillHeuristics = {
|
||||
RULES: null,
|
||||
|
||||
/**
|
||||
* Try to find a contiguous sub-array within an array.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @param {Array} subArray
|
||||
*
|
||||
* @returns {boolean}
|
||||
* Return whether subArray was found within the array or not.
|
||||
*/
|
||||
_matchContiguousSubArray(array, subArray) {
|
||||
return array.some((elm, i) => subArray.every((sElem, j) => sElem == array[i + j]));
|
||||
},
|
||||
|
||||
/**
|
||||
* Try to find the field that is look like a month select.
|
||||
*
|
||||
* @param {DOMElement} element
|
||||
* @returns {boolean}
|
||||
* Return true if we observe the trait of month select in
|
||||
* the current element.
|
||||
*/
|
||||
_isExpirationMonthLikely(element) {
|
||||
if (!(element instanceof Ci.nsIDOMHTMLSelectElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const options = [...element.options];
|
||||
const desiredValues = Array(12).fill(1).map((v, i) => v + i);
|
||||
|
||||
// The number of month options shouldn't be less than 12 or larger than 13
|
||||
// including the default option.
|
||||
if (options.length < 12 || options.length > 13) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._matchContiguousSubArray(options.map(e => +e.value), desiredValues) ||
|
||||
this._matchContiguousSubArray(options.map(e => +e.label), desiredValues);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Try to find the field that is look like a year select.
|
||||
*
|
||||
* @param {DOMElement} element
|
||||
* @returns {boolean}
|
||||
* Return true if we observe the trait of year select in
|
||||
* the current element.
|
||||
*/
|
||||
_isExpirationYearLikely(element) {
|
||||
if (!(element instanceof Ci.nsIDOMHTMLSelectElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const options = [...element.options];
|
||||
// A normal expiration year select should contain at least the last three years
|
||||
// in the list.
|
||||
const curYear = new Date().getFullYear();
|
||||
const desiredValues = Array(3).fill(0).map((v, i) => v + curYear + i);
|
||||
|
||||
return this._matchContiguousSubArray(options.map(e => +e.value), desiredValues) ||
|
||||
this._matchContiguousSubArray(options.map(e => +e.label), desiredValues);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Try to match the telephone related fields to the grammar
|
||||
* list to see if there is any valid telephone set and correct their
|
||||
|
@ -392,6 +456,72 @@ this.FormAutofillHeuristics = {
|
|||
return parsedFields;
|
||||
},
|
||||
|
||||
/**
|
||||
* Try to look for expiration date fields and revise the field names if needed.
|
||||
*
|
||||
* @param {FieldScanner} fieldScanner
|
||||
* The current parsing status for all elements
|
||||
* @returns {boolean}
|
||||
* Return true if there is any field can be recognized in the parser,
|
||||
* otherwise false.
|
||||
*/
|
||||
_parseCreditCardExpirationDateFields(fieldScanner) {
|
||||
if (fieldScanner.parsingFinished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const savedIndex = fieldScanner.parsingIndex;
|
||||
const monthAndYearFieldNames = ["cc-exp-month", "cc-exp-year"];
|
||||
const detail = fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex);
|
||||
const element = detail.elementWeakRef.get();
|
||||
|
||||
// Skip the uninteresting fields
|
||||
if (!detail || !["cc-exp", ...monthAndYearFieldNames].includes(detail.fieldName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the input type is a month picker, then assume it's cc-exp.
|
||||
if (element.type == "month") {
|
||||
fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp");
|
||||
fieldScanner.parsingIndex++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't process the fields if expiration month and expiration year are already
|
||||
// matched by regex in correct order.
|
||||
if (fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex++).fieldName == "cc-exp-month" &&
|
||||
!fieldScanner.parsingFinished &&
|
||||
fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex++).fieldName == "cc-exp-year") {
|
||||
return true;
|
||||
}
|
||||
fieldScanner.parsingIndex = savedIndex;
|
||||
|
||||
// Determine the field name by checking if the fields are month select and year select
|
||||
// likely.
|
||||
if (this._isExpirationMonthLikely(element)) {
|
||||
fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp-month");
|
||||
fieldScanner.parsingIndex++;
|
||||
const nextDetail = fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex);
|
||||
const nextElement = nextDetail.elementWeakRef.get();
|
||||
if (this._isExpirationYearLikely(nextElement) && !fieldScanner.parsingFinished) {
|
||||
fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp-year");
|
||||
fieldScanner.parsingIndex++;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
fieldScanner.parsingIndex = savedIndex;
|
||||
|
||||
// If no possible regular expiration fields are detected in current parsing window
|
||||
// fallback to "cc-exp" as there's no such case that cc-exp-month or cc-exp-year
|
||||
// presents alone.
|
||||
fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp");
|
||||
fieldScanner.parsingIndex++;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function should provide all field details of a form. The details
|
||||
* contain the autocomplete info (e.g. fieldName, section, etc).
|
||||
|
@ -417,10 +547,11 @@ this.FormAutofillHeuristics = {
|
|||
while (!fieldScanner.parsingFinished) {
|
||||
let parsedPhoneFields = this._parsePhoneFields(fieldScanner);
|
||||
let parsedAddressFields = this._parseAddressFields(fieldScanner);
|
||||
let parsedExpirationDateFields = this._parseCreditCardExpirationDateFields(fieldScanner);
|
||||
|
||||
// If there is no any field parsed, the parsing cursor can be moved
|
||||
// forward to the next one.
|
||||
if (!parsedPhoneFields && !parsedAddressFields) {
|
||||
if (!parsedPhoneFields && !parsedAddressFields && !parsedExpirationDateFields) {
|
||||
fieldScanner.parsingIndex++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -178,7 +178,7 @@ this.FormAutofillUtils = {
|
|||
return doc.querySelectorAll("input, select");
|
||||
},
|
||||
|
||||
ALLOWED_TYPES: ["text", "email", "tel", "number"],
|
||||
ALLOWED_TYPES: ["text", "email", "tel", "number", "month"],
|
||||
isFieldEligibleForAutofill(element) {
|
||||
let tagName = element.tagName;
|
||||
if (tagName == "INPUT") {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Heuristics cc-exp field test page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Heuristics cc-exp field test page</h1>
|
||||
<form id="form">
|
||||
<p><label>Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<p><label>Expiration month: <input id="cc-exp-month" autocomplete="cc-exp-month"></label></p>
|
||||
<p><label>Expiration year: <input id="cc-exp-year" autocomplete="cc-exp-year"></label></p>
|
||||
<p><label>CSC: <input id="cc-csc" autocomplete="cc-csc"></label></p>
|
||||
</form>
|
||||
<form id="formB">
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<p><label>Expiration Date: <input autocomplete="cc-exp"></label></p>
|
||||
</form>
|
||||
<form id="formC">
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<p><label>Expiration Date: <input type="text"></label></p>
|
||||
</form>
|
||||
<form id="formD">
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<p>
|
||||
<label>Exp:
|
||||
<select>
|
||||
<option value="1"></option>
|
||||
<option value="2"></option>
|
||||
<option value="3"></option>
|
||||
<option value="4"></option>
|
||||
<option value="5"></option>
|
||||
<option value="6"></option>
|
||||
<option value="7"></option>
|
||||
<option value="8"></option>
|
||||
<option value="9"></option>
|
||||
<option value="10"></option>
|
||||
<option value="11"></option>
|
||||
<option value="12"></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Exp:
|
||||
<select>
|
||||
<option value="2015"></option>
|
||||
<option value="2016"></option>
|
||||
<option value="2017"></option>
|
||||
<option value="2018"></option>
|
||||
<option value="2019"></option>
|
||||
<option value="2020"></option>
|
||||
<option value="2021"></option>
|
||||
<option value="2022"></option>
|
||||
<option value="2023"></option>
|
||||
<option value="2024"></option>
|
||||
<option value="2025"></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -134,6 +134,7 @@ function runHeuristicsTest(patterns, fixturePathPrefix) {
|
|||
forms.forEach((form, formIndex) => {
|
||||
let formInfo = FormAutofillHeuristics.getFormInfo(form);
|
||||
do_print("FieldName Prediction Results: " + formInfo.map(i => i.fieldName));
|
||||
do_print("FieldName Expected Results: " + testPattern.expectedResult[formIndex].map(i => i.fieldName));
|
||||
Assert.equal(formInfo.length, testPattern.expectedResult[formIndex].length, "Expected field count.");
|
||||
formInfo.forEach((field, fieldIndex) => {
|
||||
let expectedField = testPattern.expectedResult[formIndex][fieldIndex];
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* global runHeuristicsTest */
|
||||
|
||||
"use strict";
|
||||
|
||||
runHeuristicsTest([
|
||||
{
|
||||
fixturePath: "heuristics_cc_exp.html",
|
||||
expectedResult: [
|
||||
[
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
|
||||
],
|
||||
[
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
|
||||
],
|
||||
[
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
|
||||
],
|
||||
[
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
],
|
||||
],
|
||||
},
|
||||
], "../../fixtures/");
|
||||
|
|
@ -44,9 +44,7 @@ runHeuristicsTest([
|
|||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
|
||||
|
||||
// FIXME: bug 1392940 - the below element can not match to "cc-exp-year" regexp directly.
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
|
||||
],
|
||||
|
|
|
@ -49,9 +49,7 @@ runHeuristicsTest([
|
|||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
|
||||
|
||||
// FIXME: bug 1392940 - the below element can not match to "cc-exp-year" regexp directly.
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"}, // ac-off
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"}, // ac-off
|
||||
|
|
|
@ -17,10 +17,10 @@ runHeuristicsTest([
|
|||
// FIXME: bug 1392947 - this is a compound cc-exp field rather than the
|
||||
// separated ones below. the birthday fields are misdetected as
|
||||
// cc-exp-year and cc-exp-month.
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
|
||||
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
|
||||
],
|
||||
|
@ -42,10 +42,10 @@ runHeuristicsTest([
|
|||
// FIXME: bug 1392947 - this is a compound cc-exp field rather than the
|
||||
// separated ones below. the birthday fields are misdetected as
|
||||
// cc-exp-year and cc-exp-month.
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"}, // select
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"}, // select
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
|
||||
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
|
||||
],
|
||||
|
|
|
@ -84,7 +84,7 @@ runHeuristicsTest([
|
|||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"},
|
||||
|
||||
// FIXME: bug 1392947 - this is for birthday actually.
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-month"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-day"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-year"},
|
||||
|
|
|
@ -34,11 +34,7 @@ runHeuristicsTest([
|
|||
expectedResult: [
|
||||
[
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
|
||||
|
||||
// FIXME: bug 1392940 - Since any credit card fields should be
|
||||
// recognized no matter it's autocomplete="off" or not. This field
|
||||
// "cc-exp-month" should be fixed as "cc-exp".
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
|
||||
],
|
||||
],
|
||||
|
@ -47,11 +43,7 @@ runHeuristicsTest([
|
|||
expectedResult: [
|
||||
[
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
|
||||
|
||||
// FIXME: bug 1392940 - Since any credit card fields should be
|
||||
// recognized no matter it's autocomplete="off" or not. This field
|
||||
// "cc-exp-month" should be fixed as "cc-exp".
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
|
||||
],
|
||||
],
|
||||
|
|
|
@ -5,6 +5,7 @@ support-files =
|
|||
../fixtures/**
|
||||
|
||||
[heuristics/test_basic.js]
|
||||
[heuristics/test_cc_exp.js]
|
||||
[heuristics/third_party/test_BestBuy.js]
|
||||
[heuristics/third_party/test_CDW.js]
|
||||
[heuristics/third_party/test_CostCo.js]
|
||||
|
|
|
@ -23,7 +23,8 @@ INSTALLER_FILES += \
|
|||
endif
|
||||
|
||||
BRANDING_FILES = \
|
||||
bgstub.bmp \
|
||||
bgstub.jpg \
|
||||
bgstub_2x.jpg \
|
||||
branding.nsi \
|
||||
firefox64.ico \
|
||||
wizHeader.bmp \
|
||||
|
|
|
@ -469,7 +469,8 @@ Function .onInit
|
|||
CreateFont $FontCheckbox "$0" "10" "400"
|
||||
|
||||
InitPluginsDir
|
||||
File /oname=$PLUGINSDIR\bgstub.bmp "bgstub.bmp"
|
||||
File /oname=$PLUGINSDIR\bgstub.jpg "bgstub.jpg"
|
||||
File /oname=$PLUGINSDIR\bgstub_2x.jpg "bgstub_2x.jpg"
|
||||
|
||||
SetShellVarContext all ; Set SHCTX to All Users
|
||||
; If the user doesn't have write access to the installation directory set
|
||||
|
@ -565,6 +566,26 @@ Function .onUserAbort
|
|||
Abort
|
||||
FunctionEnd
|
||||
|
||||
Function DrawBackgroundImage
|
||||
${NSD_CreateBitmap} 0 0 100% 100% ""
|
||||
Pop $HwndBgBitmapControl
|
||||
|
||||
; If the scaling factor is 100%, use the 1x image; the 2x images scaled down
|
||||
; by that much don't always look good because some of them use thinner lines
|
||||
; and a darker background (over which aliasing is more visible).
|
||||
System::Call 'user32::GetWindowDC(i $HWNDPARENT) i .r0'
|
||||
System::Call 'gdi32::GetDeviceCaps(i $0, i 88) i .r1' ; 88 = LOGPIXELSX
|
||||
System::Call 'user32::ReleaseDC(i $HWNDPARENT, i $0)'
|
||||
${If} $1 <= 96
|
||||
${SetStretchedImageOLE} $HwndBgBitmapControl $PLUGINSDIR\bgstub.jpg $BgBitmapImage
|
||||
${Else}
|
||||
${SetStretchedImageOLE} $HwndBgBitmapControl $PLUGINSDIR\bgstub_2x.jpg $BgBitmapImage
|
||||
${EndIf}
|
||||
|
||||
; transparent bg on control prevents flicker on redraw
|
||||
SetCtlColors $HwndBgBitmapControl ${INSTALL_BLURB_TEXT_COLOR} transparent
|
||||
FunctionEnd
|
||||
|
||||
Function createProfileCleanup
|
||||
Call ShouldPromptForProfileCleanup
|
||||
${Select} $ProfileCleanupPromptType
|
||||
|
@ -702,11 +723,7 @@ Function createProfileCleanup
|
|||
SendMessage $0 ${WM_SETFONT} $FontFooter 0
|
||||
SetCtlColors $0 ${INSTALL_BLURB_TEXT_COLOR} transparent
|
||||
|
||||
${NSD_CreateBitmap} 0 0 100% 100% ""
|
||||
Pop $HwndBgBitmapControl
|
||||
${NSD_SetStretchedImage} $HwndBgBitmapControl $PLUGINSDIR\bgstub.bmp $BgBitmapImage
|
||||
; transparent bg on control prevents flicker on redraw
|
||||
SetCtlColors $HwndBgBitmapControl ${INSTALL_BLURB_TEXT_COLOR} transparent
|
||||
Call DrawBackgroundImage
|
||||
|
||||
LockWindow off
|
||||
nsDialogs::Show
|
||||
|
@ -784,11 +801,7 @@ Function createInstall
|
|||
SendMessage $Progressbar ${PBM_SETMARQUEE} 1 \
|
||||
$ProgressbarMarqueeIntervalMS ; start=1|stop=0 interval(ms)=+N
|
||||
|
||||
${NSD_CreateBitmap} 0 0 100% 100% ""
|
||||
Pop $HwndBgBitmapControl
|
||||
${NSD_SetStretchedImage} $HwndBgBitmapControl $PLUGINSDIR\bgstub.bmp $BgBitmapImage
|
||||
; transparent bg on control prevents flicker on redraw
|
||||
SetCtlColors $HwndBgBitmapControl ${INSTALL_BLURB_TEXT_COLOR} transparent
|
||||
Call DrawBackgroundImage
|
||||
|
||||
GetDlgItem $0 $HWNDPARENT 1 ; Install button
|
||||
EnableWindow $0 0
|
||||
|
|
|
@ -294,7 +294,6 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
|||
<!ENTITY newNavigatorCmd.accesskey "N">
|
||||
<!ENTITY newPrivateWindow.label "New Private Window">
|
||||
<!ENTITY newPrivateWindow.accesskey "W">
|
||||
<!ENTITY newNonRemoteWindow.label "New Non-e10s Window">
|
||||
|
||||
<!ENTITY editMenu.label "Edit">
|
||||
<!ENTITY editMenu.accesskey "E">
|
||||
|
|
|
@ -101,9 +101,6 @@ panic-button.tooltiptext = Forget about some browsing history
|
|||
devtools-webide-button2.label = WebIDE
|
||||
devtools-webide-button2.tooltiptext = Open WebIDE (%S)
|
||||
|
||||
e10s-button.label = New Non-e10s Window
|
||||
e10s-button.tooltiptext = Open a new Non-e10s Window
|
||||
|
||||
toolbarspring.label = Flexible Space
|
||||
toolbarseparator.label = Separator
|
||||
toolbarspacer.label = Space
|
||||
|
|
|
@ -44,5 +44,3 @@
|
|||
<!ENTITY useFirefoxSync.label "Tip: This uses separate profiles. Use Sync to share data between them.">
|
||||
<!ENTITY getStarted.notloggedin.label "Sign in to &syncBrand.shortName.label;…">
|
||||
<!ENTITY getStarted.configured.label "Open &syncBrand.shortName.label; preferences">
|
||||
|
||||
<!ENTITY e10sEnabled.label "Enable multi-process &brandShortName;">
|
||||
|
|
|
@ -509,9 +509,9 @@ tabbrowser {
|
|||
|
||||
/* Pinned tab separators need position: absolute when positioned (during overflow). */
|
||||
#tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned]::after {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
@ -558,14 +558,8 @@ tabbrowser {
|
|||
.tabbrowser-tab::after,
|
||||
.tabbrowser-tab::before {
|
||||
border-left: 1px solid;
|
||||
/* Vertical margin doesn't work here for positioned pinned tabs, see
|
||||
bug 1198236 and bug 1300410. We're using linear-gradient instead
|
||||
to cut off the border at the top and at the bottom. */
|
||||
border-image: linear-gradient(transparent 6px,
|
||||
currentColor 6px,
|
||||
currentColor calc(100% - 5px),
|
||||
transparent calc(100% - 5px));
|
||||
border-image-slice: 1;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 4px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
|
@ -579,11 +573,8 @@ tabbrowser {
|
|||
/* Show full height tab separators on hover. */
|
||||
.tabbrowser-tab:hover::after,
|
||||
#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab[beforehovered]::after {
|
||||
border-image: linear-gradient(transparent calc(1px + var(--tabs-top-border-width)),
|
||||
currentColor calc(1px + var(--tabs-top-border-width)),
|
||||
currentColor calc(100% - 1px),
|
||||
transparent calc(100% - 1px));
|
||||
border-image-slice: 1;
|
||||
margin-top: var(--tabs-top-border-width);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Show full height tab separators on selected tabs. */
|
||||
|
@ -591,10 +582,8 @@ tabbrowser {
|
|||
#tabbrowser-tabs[movingtab] > .tabbrowser-tab[visuallyselected]::before,
|
||||
.tabbrowser-tab[visuallyselected]::after {
|
||||
border-color: var(--tabs-border);
|
||||
border-image: linear-gradient(transparent 1px,
|
||||
var(--tabs-border) 1px,
|
||||
var(--tabs-border) calc(100% - 1px - var(--tab-toolbar-navbar-overlap)),
|
||||
transparent calc(100% - 1px - var(--tab-toolbar-navbar-overlap))) 1 !important;
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--tab-toolbar-navbar-overlap);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -229,14 +229,6 @@ toolbar[brighttext] {
|
|||
list-style-image: url("chrome://browser/skin/new-window.svg");
|
||||
}
|
||||
|
||||
#e10s-button {
|
||||
list-style-image: url("chrome://browser/skin/new-window.svg");
|
||||
}
|
||||
|
||||
#e10s-button > .toolbarbutton-icon {
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
|
||||
#new-tab-button {
|
||||
list-style-image: url("chrome://browser/skin/new-tab.svg");
|
||||
}
|
||||
|
|
|
@ -66,8 +66,26 @@ prepare() {
|
|||
popd
|
||||
}
|
||||
|
||||
prepare_mingw() {
|
||||
export install_dir=$root_dir/tools/gcc/
|
||||
mkdir -p $install_dir
|
||||
export PATH=$PATH:$install_dir/bin/
|
||||
|
||||
cd $root_dir
|
||||
|
||||
git clone -n git://git.code.sf.net/p/mingw-w64/mingw-w64
|
||||
pushd mingw-w64
|
||||
git checkout $mingw_version # Asserts the integrity of the checkout (Right?)
|
||||
popd
|
||||
}
|
||||
|
||||
apply_patch() {
|
||||
if [ $# -ge 2 ]; then
|
||||
pushd $root_dir/$1
|
||||
shift
|
||||
else
|
||||
pushd $root_dir/gcc-$gcc_version
|
||||
fi
|
||||
patch -p1 < $1
|
||||
popd
|
||||
}
|
||||
|
@ -102,17 +120,6 @@ build_gcc() {
|
|||
}
|
||||
|
||||
build_gcc_and_mingw() {
|
||||
export install_dir=$root_dir/tools/gcc/
|
||||
mkdir -p $install_dir
|
||||
export PATH=$PATH:$install_dir/bin/
|
||||
|
||||
cd $root_dir
|
||||
|
||||
git clone -n git://git.code.sf.net/p/mingw-w64/mingw-w64
|
||||
pushd mingw-w64
|
||||
git checkout $mingw_version # Asserts the integrity of the checkout (Right?)
|
||||
popd
|
||||
|
||||
mkdir gcc-objdir
|
||||
pushd gcc-objdir
|
||||
../gcc-$gcc_version/configure --prefix=$install_dir --target=i686-w64-mingw32 --with-gnu-ld --with-gnu-as --disable-multilib --enable-threads=posix
|
||||
|
|
|
@ -228,12 +228,16 @@ var AnimationsController = {
|
|||
}),
|
||||
|
||||
onNewNodeFront: Task.async(function* () {
|
||||
// Ignore if the panel isn't visible or the node selection hasn't changed.
|
||||
if (!this.isPanelVisible() ||
|
||||
this.nodeFront === gInspector.selection.nodeFront) {
|
||||
// Ignore if the panel isn't visible.
|
||||
// Or the node selection hasn't changed and no animation mutations event occurs during
|
||||
// hidden.
|
||||
if (!this.isPanelVisible() || (this.nodeFront === gInspector.selection.nodeFront &&
|
||||
!this.mutationsDetectedWhileHidden)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mutationsDetectedWhileHidden = false;
|
||||
|
||||
this.nodeFront = gInspector.selection.nodeFront;
|
||||
let done = gInspector.updating("animationscontroller");
|
||||
|
||||
|
@ -361,8 +365,14 @@ var AnimationsController = {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.isPanelVisible()) {
|
||||
// Let the UI know the list has been updated.
|
||||
this.emit(this.PLAYERS_UPDATED_EVENT, this.animationPlayers);
|
||||
} else {
|
||||
// Avoid updating the UI while the panel is hidden.
|
||||
// This avoids unnecessary work.
|
||||
this.mutationsDetectedWhileHidden = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -328,12 +328,15 @@ AnimationsTimeline.prototype = {
|
|||
continue;
|
||||
}
|
||||
if (i === index) {
|
||||
if (this.animationRootEl.classList.contains("animation-detail-visible")) {
|
||||
// Already the animation is selected.
|
||||
this.emit("animation-already-selected", this.animations[i]);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
animationEl.classList.remove("selected");
|
||||
this.emit("animation-unselected", this.animations[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ skip-if = os == "linux" && !debug # Bug 1234567
|
|||
[browser_animation_refresh_on_removed_animation.js]
|
||||
skip-if = os == "linux" && !debug # Bug 1227792
|
||||
[browser_animation_refresh_when_active.js]
|
||||
[browser_animation_refresh_when_active_after_mutations.js]
|
||||
[browser_animation_running_on_compositor.js]
|
||||
[browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
|
||||
[browser_animation_shows_player_on_valid_node.js]
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
// 4. Display from first time if displayed animation is only one.
|
||||
// 5. Close the animation-detail element by clicking on close button.
|
||||
// 6. Stay selected animation even if refresh all UI.
|
||||
// 7. Close the animation-detail element again and click selected animation again.
|
||||
|
||||
requestLongerTimeout(5);
|
||||
|
||||
|
@ -28,7 +29,7 @@ add_task(function* () {
|
|||
ok(animationDetailEl, "The animation-detail element should exist");
|
||||
|
||||
// 2. Hidden at first if multiple animations were displayed.
|
||||
const win = animationDetailEl.ownerDocument.defaultView;
|
||||
const win = timelineComponent.rootWrapperEl.ownerGlobal;
|
||||
is(win.getComputedStyle(splitboxControlledEl).display, "none",
|
||||
"The animation-detail element should be hidden at first "
|
||||
+ "if multiple animations were displayed");
|
||||
|
@ -45,10 +46,7 @@ add_task(function* () {
|
|||
|
||||
// 5. Close the animation-detail element by clicking on close button.
|
||||
const previousHeight = animationDetailEl.offsetHeight;
|
||||
const button = animationDetailEl.querySelector(".animation-detail-header button");
|
||||
const onclosed = timelineComponent.once("animation-detail-closed");
|
||||
EventUtils.sendMouseEvent({type: "click"}, button, win);
|
||||
yield onclosed;
|
||||
yield clickCloseButtonForDetailPanel(timelineComponent, animationDetailEl);
|
||||
is(win.getComputedStyle(splitboxControlledEl).display, "none",
|
||||
"animation-detail element should not display");
|
||||
|
||||
|
@ -64,4 +62,24 @@ add_task(function* () {
|
|||
yield clickTimelineRewindButton(panel);
|
||||
ok(animationDetailEl.querySelector(".property"),
|
||||
"The property in animation-detail element should stay as is");
|
||||
|
||||
// 7. Close the animation-detail element again and click selected animation again.
|
||||
yield clickCloseButtonForDetailPanel(timelineComponent, animationDetailEl);
|
||||
yield clickOnAnimation(panel, 0);
|
||||
isnot(win.getComputedStyle(splitboxControlledEl).display, "none",
|
||||
"animation-detail element should display again");
|
||||
});
|
||||
|
||||
/**
|
||||
* Click close button for animation-detail panel.
|
||||
*
|
||||
* @param {AnimationTimeline} AnimationTimeline component
|
||||
* @param {DOMNode} animation-detail element
|
||||
* @return {Promise} which wait for close the detail pane
|
||||
*/
|
||||
function* clickCloseButtonForDetailPanel(timeline, element) {
|
||||
const button = element.querySelector(".animation-detail-header button");
|
||||
const onclosed = timeline.once("animation-detail-closed");
|
||||
EventUtils.sendMouseEvent({type: "click"}, button, element.ownerDocument.defaultView);
|
||||
return yield onclosed;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
// Test that refresh animation UI while the panel is hidden.
|
||||
|
||||
const EXPECTED_GRAPH_PATH_SEGMENTS = [{ x: 0, y: 0 },
|
||||
{ x: 49999, y: 0.0 },
|
||||
{ x: 50000, y: 0.5 },
|
||||
{ x: 99999, y: 0.5 },
|
||||
{ x: 100000, y: 0 }];
|
||||
|
||||
add_task(function* () {
|
||||
info("Open animation inspector once so that activate animation mutations listener");
|
||||
yield addTab("data:text/html;charset=utf8,<div id='target'>test</div>");
|
||||
const { controller, inspector, panel } = yield openAnimationInspector();
|
||||
|
||||
info("Select other tool to hide animation inspector");
|
||||
yield inspector.sidebar.select("ruleview");
|
||||
|
||||
// Count players-updated event in controller.
|
||||
let updatedEventCount = 0;
|
||||
controller.on("players-updated", () => {
|
||||
updatedEventCount += 1;
|
||||
});
|
||||
|
||||
info("Make animation by eval in content");
|
||||
yield evalInDebuggee(gBrowser.selectedBrowser.messageManager,
|
||||
`document.querySelector('#target').animate(
|
||||
{ transform: 'translate(100px)' },
|
||||
{ duration: 100000, easing: 'steps(2)' });`);
|
||||
info("Wait for animation mutations event");
|
||||
yield controller.animationsFront.once("mutations");
|
||||
info("Check players-updated events count");
|
||||
is(updatedEventCount, 0, "players-updated event shoud not be fired");
|
||||
|
||||
info("Re-select animation inspector and check the UI");
|
||||
yield inspector.sidebar.select("animationinspector");
|
||||
yield waitForAnimationTimelineRendering(panel);
|
||||
|
||||
const timeBlocks = getAnimationTimeBlocks(panel);
|
||||
is(timeBlocks.length, 1, "One animation should display");
|
||||
const timeBlock = timeBlocks[0];
|
||||
const state = timeBlock.animation.state;
|
||||
const effectEl = timeBlock.containerEl.querySelector(".effect-easing");
|
||||
ok(effectEl, "<g> element for effect easing should exist");
|
||||
const pathEl = effectEl.querySelector("path");
|
||||
ok(pathEl, "<path> element for effect easing should exist");
|
||||
assertPathSegments(pathEl, state.duration, false, EXPECTED_GRAPH_PATH_SEGMENTS);
|
||||
});
|
|
@ -67,6 +67,8 @@ support-files =
|
|||
[browser_dbg-breakpoints-reloading.js]
|
||||
skip-if = true # Bug 1383576
|
||||
[browser_dbg-breakpoints-cond.js]
|
||||
[browser_dbg-browser-content-toolbox.js]
|
||||
skip-if = !e10s # This test is only valid in e10s
|
||||
[browser_dbg-call-stack.js]
|
||||
[browser_dbg-scopes.js]
|
||||
[browser_dbg-chrome-create.js]
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the debugger is succesfully loaded in the Browser Content Toolbox.
|
||||
*/
|
||||
|
||||
const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
|
||||
|
||||
function toggleBreakpoint(dbg, index) {
|
||||
const bp = findElement(dbg, "breakpointItem", index);
|
||||
const input = bp.querySelector("input");
|
||||
input.click();
|
||||
}
|
||||
|
||||
async function disableBreakpoint(dbg, index) {
|
||||
const disabled = waitForDispatch(dbg, "DISABLE_BREAKPOINT");
|
||||
toggleBreakpoint(dbg, index);
|
||||
await disabled;
|
||||
}
|
||||
|
||||
async function enableBreakpoint(dbg, index) {
|
||||
const enabled = waitForDispatch(dbg, "ENABLE_BREAKPOINT");
|
||||
toggleBreakpoint(dbg, index);
|
||||
await enabled;
|
||||
}
|
||||
|
||||
function findBreakpoint(dbg, url, line) {
|
||||
const { selectors: { getBreakpoint }, getState } = dbg;
|
||||
const source = findSource(dbg, url);
|
||||
return getBreakpoint(getState(), { sourceId: source.id, line });
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
clearDebuggerPreferences();
|
||||
|
||||
info("Open a tab pointing to doc-scripts.html");
|
||||
await addTab(EXAMPLE_URL + "doc-scripts.html");
|
||||
|
||||
info("Open the Browser Content Toolbox");
|
||||
let toolbox = await gDevToolsBrowser.openContentProcessToolbox(gBrowser);
|
||||
|
||||
info("Wait for the debugger to be ready");
|
||||
await toolbox.getPanelWhenReady("jsdebugger");
|
||||
|
||||
let dbg = createDebuggerContext(toolbox);
|
||||
ok(dbg, "Debugger context is available");
|
||||
|
||||
info("Create a breakpoint");
|
||||
await selectSource(dbg, "simple2");
|
||||
await addBreakpoint(dbg, "simple2", 3);
|
||||
|
||||
info("Disable the breakpoint");
|
||||
await disableBreakpoint(dbg, 1);
|
||||
let bp = findBreakpoint(dbg, "simple2", 3);
|
||||
is(bp.disabled, true, "breakpoint is disabled");
|
||||
|
||||
info("Enable the breakpoint");
|
||||
await enableBreakpoint(dbg, 1);
|
||||
bp = findBreakpoint(dbg, "simple2", 3);
|
||||
is(bp.disabled, false, "breakpoint is enabled");
|
||||
|
||||
info("Close the browser toolbox window");
|
||||
let onToolboxDestroyed = toolbox.once("destroyed");
|
||||
toolbox.win.top.close();
|
||||
await onToolboxDestroyed;
|
||||
|
||||
info("Toolbox is destroyed");
|
||||
});
|
|
@ -366,23 +366,30 @@ function createDebuggerContext(toolbox) {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Intilializes the debugger.
|
||||
*
|
||||
* @memberof mochitest
|
||||
* @param {String} url
|
||||
* @param {Array} sources
|
||||
* @return {Promise} dbg
|
||||
* @static
|
||||
* Clear all the debugger related preferences.
|
||||
*/
|
||||
function initDebugger(url, ...sources) {
|
||||
return Task.spawn(function*() {
|
||||
function clearDebuggerPreferences() {
|
||||
Services.prefs.clearUserPref("devtools.debugger.pause-on-exceptions");
|
||||
Services.prefs.clearUserPref("devtools.debugger.ignore-caught-exceptions");
|
||||
Services.prefs.clearUserPref("devtools.debugger.tabs");
|
||||
Services.prefs.clearUserPref("devtools.debugger.pending-selected-location");
|
||||
Services.prefs.clearUserPref("devtools.debugger.pending-breakpoints");
|
||||
Services.prefs.clearUserPref("devtools.debugger.expressions");
|
||||
}
|
||||
|
||||
/**
|
||||
* Intilializes the debugger.
|
||||
*
|
||||
* @memberof mochitest
|
||||
* @param {String} url
|
||||
* @return {Promise} dbg
|
||||
* @static
|
||||
*/
|
||||
function initDebugger(url) {
|
||||
return Task.spawn(function*() {
|
||||
clearDebuggerPreferences();
|
||||
const toolbox = yield openNewTabAndToolbox(EXAMPLE_URL + url, "jsdebugger");
|
||||
return createDebuggerContext(toolbox);
|
||||
});
|
||||
|
|
|
@ -363,7 +363,13 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
return deferred.promise;
|
||||
},
|
||||
|
||||
// Used by menus.js
|
||||
/**
|
||||
* Open the Browser Content Toolbox for the provided gBrowser instance.
|
||||
* Returns a promise that resolves with a toolbox instance. If no content process is
|
||||
* available, the promise will be rejected and a message will be displayed to the user.
|
||||
*
|
||||
* Used by menus.js
|
||||
*/
|
||||
openContentProcessToolbox(gBrowser) {
|
||||
let { childCount } = Services.ppmm;
|
||||
// Get the process message manager for the current tab
|
||||
|
@ -377,16 +383,17 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
}
|
||||
}
|
||||
if (processId) {
|
||||
this._getContentProcessTarget(processId)
|
||||
return this._getContentProcessTarget(processId)
|
||||
.then(target => {
|
||||
// Display a new toolbox, in a new window, with debugger by default
|
||||
return gDevTools.showToolbox(target, "jsdebugger",
|
||||
Toolbox.HostType.WINDOW);
|
||||
});
|
||||
} else {
|
||||
}
|
||||
|
||||
let msg = L10N.getStr("toolbox.noContentProcessForTab.message");
|
||||
Services.prompt.alert(null, "", msg);
|
||||
}
|
||||
return Promise.reject(msg);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -138,7 +138,8 @@ SourceMapURLService.prototype._onNewStyleSheet = function (sheet) {
|
|||
return;
|
||||
}
|
||||
|
||||
let {href: url, sourceMapURL, actor: id} = sheet._form;
|
||||
let {href, nodeHref, sourceMapURL, actorID: id} = sheet;
|
||||
let url = href || nodeHref;
|
||||
this._urls.set(url, { id, url, sourceMapURL});
|
||||
this._idMap.set(id, url);
|
||||
};
|
||||
|
|
|
@ -10,7 +10,6 @@ const ToolDefinitions = require("devtools/client/definitions").Tools;
|
|||
const CssLogic = require("devtools/shared/inspector/css-logic");
|
||||
const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
|
||||
const promise = require("promise");
|
||||
const Services = require("Services");
|
||||
const OutputParser = require("devtools/client/shared/output-parser");
|
||||
const {PrefObserver} = require("devtools/client/shared/prefs");
|
||||
const {createChild} = require("devtools/client/inspector/shared/utils");
|
||||
|
@ -37,8 +36,6 @@ const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.prope
|
|||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
|
||||
|
||||
const PREF_ORIG_SOURCES = "devtools.source-map.client-service.enabled";
|
||||
|
||||
const FILTER_CHANGED_TIMEOUT = 150;
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
|
@ -205,9 +202,7 @@ function CssComputedView(inspector, document, pageStyle) {
|
|||
// Refresh panel when color unit changed or pref for showing
|
||||
// original sources changes.
|
||||
this._handlePrefChange = this._handlePrefChange.bind(this);
|
||||
this._onSourcePrefChanged = this._onSourcePrefChanged.bind(this);
|
||||
this._prefObserver = new PrefObserver("devtools.");
|
||||
this._prefObserver.on(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
|
||||
this._prefObserver.on("devtools.defaultColorUnit", this._handlePrefChange);
|
||||
|
||||
// The element that we're inspecting, and the document that it comes from.
|
||||
|
@ -602,14 +597,6 @@ CssComputedView.prototype = {
|
|||
CssLogic.FILTER.USER;
|
||||
},
|
||||
|
||||
_onSourcePrefChanged: function () {
|
||||
this._handlePrefChange();
|
||||
for (let propView of this.propertyViews) {
|
||||
propView.updateSourceLinks();
|
||||
}
|
||||
this.inspector.emit("computed-view-sourcelinks-updated");
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the box model view.
|
||||
*/
|
||||
|
@ -753,7 +740,6 @@ CssComputedView.prototype = {
|
|||
this._viewedElement = null;
|
||||
this._outputParser = null;
|
||||
|
||||
this._prefObserver.off(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
|
||||
this._prefObserver.off("devtools.defaultColorUnit", this._handlePrefChange);
|
||||
this._prefObserver.destroy();
|
||||
|
||||
|
@ -1096,15 +1082,14 @@ PropertyView.prototype = {
|
|||
.getMatchedSelectors(this.tree._viewedElement, this.name)
|
||||
.then(matched => {
|
||||
if (!this.matchedExpanded) {
|
||||
return promise.resolve(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
this._matchedSelectorResponse = matched;
|
||||
|
||||
return this._buildMatchedSelectors().then(() => {
|
||||
this._buildMatchedSelectors();
|
||||
this.matchedExpander.setAttribute("open", "");
|
||||
this.tree.inspector.emit("computed-view-property-expanded");
|
||||
});
|
||||
}).catch(console.error);
|
||||
}
|
||||
|
||||
|
@ -1119,7 +1104,6 @@ PropertyView.prototype = {
|
|||
},
|
||||
|
||||
_buildMatchedSelectors: function () {
|
||||
let promises = [];
|
||||
let frag = this.element.ownerDocument.createDocumentFragment();
|
||||
|
||||
for (let selector of this.matchedSelectorViews) {
|
||||
|
@ -1157,12 +1141,10 @@ PropertyView.prototype = {
|
|||
class: "fix-get-selection computed-other-property-value theme-fg-color1"
|
||||
});
|
||||
valueDiv.appendChild(selector.outputFragment);
|
||||
promises.push(selector.ready);
|
||||
}
|
||||
|
||||
this.matchedSelectorsContainer.innerHTML = "";
|
||||
this.matchedSelectorsContainer.appendChild(frag);
|
||||
return promise.all(promises);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1180,19 +1162,6 @@ PropertyView.prototype = {
|
|||
return this._matchedSelectorViews;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update all the selector source links to reflect whether we're linking to
|
||||
* original sources (e.g. Sass files).
|
||||
*/
|
||||
updateSourceLinks: function () {
|
||||
if (!this._matchedSelectorViews) {
|
||||
return;
|
||||
}
|
||||
for (let view of this._matchedSelectorViews) {
|
||||
view.updateSourceLink();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The action when a user expands matched selectors.
|
||||
*
|
||||
|
@ -1225,6 +1194,12 @@ PropertyView.prototype = {
|
|||
* Destroy this property view, removing event listeners
|
||||
*/
|
||||
destroy: function () {
|
||||
if (this._matchedSelectorViews) {
|
||||
for (let view of this._matchedSelectorViews) {
|
||||
view.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
this.element.removeEventListener("dblclick", this.onMatchedToggle);
|
||||
this.shortcuts.destroy();
|
||||
this.element = null;
|
||||
|
@ -1253,8 +1228,26 @@ function SelectorView(tree, selectorInfo) {
|
|||
this._cacheStatusNames();
|
||||
|
||||
this.openStyleEditor = this.openStyleEditor.bind(this);
|
||||
this._updateLocation = this._updateLocation.bind(this);
|
||||
|
||||
this.ready = this.updateSourceLink();
|
||||
const rule = this.selectorInfo.rule;
|
||||
if (!rule || !rule.parentStyleSheet || rule.type == ELEMENT_STYLE) {
|
||||
this.source = CssLogic.l10n("rule.sourceElement");
|
||||
} else {
|
||||
// This always refers to the generated location.
|
||||
const sheet = rule.parentStyleSheet;
|
||||
this.source = CssLogic.shortSource(sheet) + ":" + rule.line;
|
||||
|
||||
const url = sheet.href || sheet.nodeHref;
|
||||
this.currentLocation = {
|
||||
href: url,
|
||||
line: rule.line,
|
||||
column: rule.column,
|
||||
};
|
||||
this.generatedLocation = this.currentLocation;
|
||||
this.sourceMapURLService = this.tree.inspector.toolbox.sourceMapURLService;
|
||||
this.sourceMapURLService.subscribe(url, rule.line, rule.column, this._updateLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1344,50 +1337,43 @@ SelectorView.prototype = {
|
|||
|
||||
/**
|
||||
* Update the text of the source link to reflect whether we're showing
|
||||
* original sources or not.
|
||||
* original sources or not. This is a callback for
|
||||
* SourceMapURLService.subscribe, which see.
|
||||
*
|
||||
* @param {Boolean} enabled
|
||||
* True if the passed-in location should be used; this means
|
||||
* that source mapping is in use and the remaining arguments
|
||||
* are the original location. False if the already-known
|
||||
* (stored) location should be used.
|
||||
* @param {String} url
|
||||
* The original URL
|
||||
* @param {Number} line
|
||||
* The original line number
|
||||
* @param {number} column
|
||||
* The original column number
|
||||
*/
|
||||
updateSourceLink: function () {
|
||||
return this.updateSource().then((oldSource) => {
|
||||
if (oldSource !== this.source && this.tree.element) {
|
||||
let selector = '[sourcelocation="' + oldSource + '"]';
|
||||
_updateLocation: function (enabled, url, line, column) {
|
||||
if (!this.tree.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update |currentLocation| to be whichever location is being
|
||||
// displayed at the moment.
|
||||
if (enabled) {
|
||||
this.currentLocation = { href: url, line, column };
|
||||
} else {
|
||||
this.currentLocation = this.generatedLocation;
|
||||
}
|
||||
|
||||
let selector = '[sourcelocation="' + this.source + '"]';
|
||||
let link = this.tree.element.querySelector(selector);
|
||||
if (link) {
|
||||
link.textContent = this.source;
|
||||
link.setAttribute("sourcelocation", this.source);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the 'source' store based on our original sources preference.
|
||||
*/
|
||||
updateSource: function () {
|
||||
let rule = this.selectorInfo.rule;
|
||||
this.sheet = rule.parentStyleSheet;
|
||||
|
||||
if (!rule || !this.sheet) {
|
||||
let oldSource = this.source;
|
||||
this.source = CssLogic.l10n("rule.sourceElement");
|
||||
return promise.resolve(oldSource);
|
||||
let text = CssLogic.shortSource(this.currentLocation) +
|
||||
":" + this.currentLocation.line;
|
||||
link.textContent = text;
|
||||
}
|
||||
|
||||
let showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||
|
||||
if (showOrig && rule.type !== ELEMENT_STYLE) {
|
||||
// set as this first so we show something while we're fetching
|
||||
this.source = CssLogic.shortSource(this.sheet) + ":" + rule.line;
|
||||
|
||||
return rule.getOriginalLocation().then(({href, line}) => {
|
||||
let oldSource = this.source;
|
||||
this.source = CssLogic.shortSource({href: href}) + ":" + line;
|
||||
return oldSource;
|
||||
});
|
||||
}
|
||||
|
||||
let oldSource = this.source;
|
||||
this.source = CssLogic.shortSource(this.sheet) + ":" + rule.line;
|
||||
return promise.resolve(oldSource);
|
||||
this.tree.inspector.emit("computed-view-sourcelinks-updated");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1414,21 +1400,26 @@ SelectorView.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
let location = promise.resolve(rule.location);
|
||||
if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
|
||||
location = rule.getOriginalLocation();
|
||||
}
|
||||
|
||||
location.then(({source, href, line, column}) => {
|
||||
let {href, line, column} = this.currentLocation;
|
||||
let target = inspector.target;
|
||||
if (ToolDefinitions.styleEditor.isTargetSupported(target)) {
|
||||
gDevTools.showToolbox(target, "styleeditor").then(function (toolbox) {
|
||||
let sheet = source || href;
|
||||
toolbox.getCurrentPanel().selectStyleSheet(sheet, line, column);
|
||||
toolbox.getCurrentPanel().selectStyleSheet(href, line, column);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy this selector view, removing event listeners
|
||||
*/
|
||||
destroy: function () {
|
||||
let rule = this.selectorInfo.rule;
|
||||
if (rule && rule.parentStyleSheet && rule.type != ELEMENT_STYLE) {
|
||||
const url = rule.parentStyleSheet.href || rule.parentStyleSheet.nodeHref;
|
||||
this.sourceMapURLService.unsubscribe(url, rule.line,
|
||||
rule.column, this._updateLocation);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function ComputedViewTool(inspector, window) {
|
||||
|
|
|
@ -18,22 +18,18 @@ add_task(function* () {
|
|||
|
||||
yield addTab(TESTCASE_URI);
|
||||
let {toolbox, inspector, view} = yield openComputedView();
|
||||
let onLinksUpdated = inspector.once("computed-view-sourcelinks-updated");
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
info("Expanding the first property");
|
||||
yield expandComputedViewPropertyByIndex(view, 0);
|
||||
|
||||
info("Verifying the link text");
|
||||
// Forcing a call to updateSourceLink on the SelectorView here. The
|
||||
// computed-view already does it, but we have no way of waiting for it to be
|
||||
// done here, so just call it again and wait for the returned promise to
|
||||
// resolve.
|
||||
let propertyView = getComputedViewPropertyView(view, "color");
|
||||
yield propertyView.matchedSelectorViews[0].updateSourceLink();
|
||||
yield onLinksUpdated;
|
||||
verifyLinkText(view, SCSS_LOC);
|
||||
|
||||
info("Toggling the pref");
|
||||
let onLinksUpdated = inspector.once("computed-view-sourcelinks-updated");
|
||||
onLinksUpdated = inspector.once("computed-view-sourcelinks-updated");
|
||||
Services.prefs.setBoolPref(PREF, false);
|
||||
yield onLinksUpdated;
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ Rule.prototype = {
|
|||
* The rule's line within a stylesheet
|
||||
*/
|
||||
get ruleLine() {
|
||||
return this.domRule ? this.domRule.line : "";
|
||||
return this.domRule ? this.domRule.line : -1;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -127,36 +127,6 @@ Rule.prototype = {
|
|||
return this.domRule ? this.domRule.column : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get display name for this rule based on the original source
|
||||
* for this rule's style sheet.
|
||||
*
|
||||
* @return {Promise}
|
||||
* Promise which resolves with location as an object containing
|
||||
* both the full and short version of the source string.
|
||||
*/
|
||||
getOriginalSourceStrings: function () {
|
||||
return this.domRule.getOriginalLocation().then(({href, line, mediaText}) => {
|
||||
let mediaString = mediaText ? " @" + mediaText : "";
|
||||
let linePart = line > 0 ? (":" + line) : "";
|
||||
let decodedHref = href;
|
||||
|
||||
if (decodedHref) {
|
||||
try {
|
||||
decodedHref = decodeURIComponent(href);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
let sourceStrings = {
|
||||
full: (decodedHref || CssLogic.l10n("rule.sourceInline")) + linePart +
|
||||
mediaString,
|
||||
short: CssLogic.shortSource({href: decodedHref}) + linePart + mediaString
|
||||
};
|
||||
|
||||
return sourceStrings;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the rule matches the creation options
|
||||
* specified.
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
const promise = require("promise");
|
||||
const Services = require("Services");
|
||||
const {Task} = require("devtools/shared/task");
|
||||
const {Tools} = require("devtools/client/definitions");
|
||||
const {l10n} = require("devtools/shared/inspector/css-logic");
|
||||
const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
|
||||
const OutputParser = require("devtools/client/shared/output-parser");
|
||||
|
@ -18,7 +17,6 @@ const ElementStyle = require("devtools/client/inspector/rules/models/element-sty
|
|||
const Rule = require("devtools/client/inspector/rules/models/rule");
|
||||
const RuleEditor = require("devtools/client/inspector/rules/views/rule-editor");
|
||||
const ClassListPreviewer = require("devtools/client/inspector/rules/views/class-list-previewer");
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
const {getCssProperties} = require("devtools/shared/fronts/css-properties");
|
||||
const {
|
||||
VIEW_NODE_SELECTOR_TYPE,
|
||||
|
@ -41,7 +39,6 @@ const HTML_NS = "http://www.w3.org/1999/xhtml";
|
|||
const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
|
||||
const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit";
|
||||
const FILTER_CHANGED_TIMEOUT = 150;
|
||||
const PREF_ORIG_SOURCES = "devtools.source-map.client-service.enabled";
|
||||
|
||||
// This is used to parse user input when filtering.
|
||||
const FILTER_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*;?$/;
|
||||
|
@ -157,10 +154,8 @@ function CssRuleView(inspector, document, store, pageStyle) {
|
|||
this.focusCheckbox.addEventListener("click", this._onTogglePseudoClass);
|
||||
|
||||
this._handlePrefChange = this._handlePrefChange.bind(this);
|
||||
this._onSourcePrefChanged = this._onSourcePrefChanged.bind(this);
|
||||
|
||||
this._prefObserver = new PrefObserver("devtools.");
|
||||
this._prefObserver.on(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
|
||||
this._prefObserver.on(PREF_UA_STYLES, this._handlePrefChange);
|
||||
this._prefObserver.on(PREF_DEFAULT_COLOR_UNIT, this._handlePrefChange);
|
||||
|
||||
|
@ -557,20 +552,6 @@ CssRuleView.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update source links when pref for showing original sources changes
|
||||
*/
|
||||
_onSourcePrefChanged: function () {
|
||||
if (this._elementStyle && this._elementStyle.rules) {
|
||||
for (let rule of this._elementStyle.rules) {
|
||||
if (rule.editor) {
|
||||
rule.editor.updateSourceLink();
|
||||
}
|
||||
}
|
||||
this.inspector.emit("rule-view-sourcelinks-updated");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the filter style search value.
|
||||
* @param {String} value
|
||||
|
@ -682,7 +663,6 @@ CssRuleView.prototype = {
|
|||
|
||||
this._dummyElement = null;
|
||||
|
||||
this._prefObserver.off(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
|
||||
this._prefObserver.off(PREF_UA_STYLES, this._handlePrefChange);
|
||||
this._prefObserver.off(PREF_DEFAULT_COLOR_UNIT, this._handlePrefChange);
|
||||
this._prefObserver.destroy();
|
||||
|
@ -1606,7 +1586,6 @@ function RuleViewTool(inspector, window) {
|
|||
|
||||
this.clearUserProperties = this.clearUserProperties.bind(this);
|
||||
this.refresh = this.refresh.bind(this);
|
||||
this.onLinkClicked = this.onLinkClicked.bind(this);
|
||||
this.onMutations = this.onMutations.bind(this);
|
||||
this.onPanelSelected = this.onPanelSelected.bind(this);
|
||||
this.onPropertyChanged = this.onPropertyChanged.bind(this);
|
||||
|
@ -1616,7 +1595,6 @@ function RuleViewTool(inspector, window) {
|
|||
|
||||
this.view.on("ruleview-changed", this.onPropertyChanged);
|
||||
this.view.on("ruleview-refreshed", this.onViewRefreshed);
|
||||
this.view.on("ruleview-linked-clicked", this.onLinkClicked);
|
||||
|
||||
this.inspector.selection.on("detached-front", this.onSelected);
|
||||
this.inspector.selection.on("new-node-front", this.onSelected);
|
||||
|
@ -1688,33 +1666,6 @@ RuleViewTool.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
onLinkClicked: function (e, rule) {
|
||||
let sheet = rule.parentStyleSheet;
|
||||
|
||||
// Chrome stylesheets are not listed in the style editor, so show
|
||||
// these sheets in the view source window instead.
|
||||
if (!sheet || sheet.isSystem) {
|
||||
let href = rule.nodeHref || rule.href;
|
||||
let toolbox = gDevTools.getToolbox(this.inspector.target);
|
||||
toolbox.viewSource(href, rule.line);
|
||||
return;
|
||||
}
|
||||
|
||||
let location = promise.resolve(rule.location);
|
||||
if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
|
||||
location = rule.getOriginalLocation();
|
||||
}
|
||||
location.then(({ source, href, line, column }) => {
|
||||
let target = this.inspector.target;
|
||||
if (Tools.styleEditor.isTargetSupported(target)) {
|
||||
gDevTools.showToolbox(target, "styleeditor").then(function (toolbox) {
|
||||
let url = source || href;
|
||||
toolbox.getCurrentPanel().selectStyleSheet(url, line, column);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onPropertyChanged: function () {
|
||||
this.inspector.markDirty();
|
||||
},
|
||||
|
@ -1757,7 +1708,6 @@ RuleViewTool.prototype = {
|
|||
this.inspector.pageStyle.off("stylesheet-updated", this.refresh);
|
||||
}
|
||||
|
||||
this.view.off("ruleview-linked-clicked", this.onLinkClicked);
|
||||
this.view.off("ruleview-changed", this.onPropertyChanged);
|
||||
this.view.off("ruleview-refreshed", this.onViewRefreshed);
|
||||
|
||||
|
|
|
@ -27,25 +27,20 @@ const promise = require("promise");
|
|||
const Services = require("Services");
|
||||
const EventEmitter = require("devtools/shared/old-event-emitter");
|
||||
const {Task} = require("devtools/shared/task");
|
||||
const {Tools} = require("devtools/client/definitions");
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
const CssLogic = require("devtools/shared/inspector/css-logic");
|
||||
|
||||
const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
|
||||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
|
||||
|
||||
const PREF_ORIG_SOURCES = "devtools.source-map.client-service.enabled";
|
||||
|
||||
/**
|
||||
* RuleEditor is responsible for the following:
|
||||
* Owns a Rule object and creates a list of TextPropertyEditors
|
||||
* for its TextProperties.
|
||||
* Manages creation of new text properties.
|
||||
*
|
||||
* One step of a RuleEditor's instantiation is figuring out what's the original
|
||||
* source link to the parent stylesheet (in case of source maps). This step is
|
||||
* asynchronous and is triggered as soon as the RuleEditor is instantiated (see
|
||||
* updateSourceLink). If you need to know when the RuleEditor is done with this,
|
||||
* you need to listen to the source-link-updated event.
|
||||
*
|
||||
* @param {CssRuleView} ruleView
|
||||
* The CssRuleView containg the document holding this rule editor.
|
||||
* @param {Rule} rule
|
||||
|
@ -57,6 +52,9 @@ function RuleEditor(ruleView, rule) {
|
|||
this.ruleView = ruleView;
|
||||
this.doc = this.ruleView.styleDocument;
|
||||
this.toolbox = this.ruleView.inspector.toolbox;
|
||||
// We stash this locally so that we can refer to it from |destroy|
|
||||
// without accidentally reinstantiating the service during shutdown.
|
||||
this.sourceMapURLService = this.toolbox.sourceMapURLService;
|
||||
this.rule = rule;
|
||||
|
||||
this.isEditable = !rule.isSystem;
|
||||
|
@ -69,10 +67,13 @@ function RuleEditor(ruleView, rule) {
|
|||
this._onSelectorDone = this._onSelectorDone.bind(this);
|
||||
this._locationChanged = this._locationChanged.bind(this);
|
||||
this.updateSourceLink = this.updateSourceLink.bind(this);
|
||||
this._onToolChanged = this._onToolChanged.bind(this);
|
||||
this._updateLocation = this._updateLocation.bind(this);
|
||||
this._onSourceClick = this._onSourceClick.bind(this);
|
||||
|
||||
this.rule.domRule.on("location-changed", this._locationChanged);
|
||||
this.toolbox.on("tool-registered", this.updateSourceLink);
|
||||
this.toolbox.on("tool-unregistered", this.updateSourceLink);
|
||||
this.toolbox.on("tool-registered", this._onToolChanged);
|
||||
this.toolbox.on("tool-unregistered", this._onToolChanged);
|
||||
|
||||
this._create();
|
||||
}
|
||||
|
@ -80,8 +81,21 @@ function RuleEditor(ruleView, rule) {
|
|||
RuleEditor.prototype = {
|
||||
destroy: function () {
|
||||
this.rule.domRule.off("location-changed");
|
||||
this.toolbox.off("tool-registered", this.updateSourceLink);
|
||||
this.toolbox.off("tool-unregistered", this.updateSourceLink);
|
||||
this.toolbox.off("tool-registered", this._onToolChanged);
|
||||
this.toolbox.off("tool-unregistered", this._onToolChanged);
|
||||
|
||||
let url = null;
|
||||
if (this.rule.sheet) {
|
||||
url = this.rule.sheet.href || this.rule.sheet.nodeHref;
|
||||
}
|
||||
if (url && !this.rule.isSystem && this.rule.domRule.type !== ELEMENT_STYLE) {
|
||||
// Only get the original source link if the rule isn't a system
|
||||
// rule and if it isn't an inline rule.
|
||||
let sourceLine = this.rule.ruleLine;
|
||||
let sourceColumn = this.rule.ruleColumn;
|
||||
this.sourceMapURLService.unsubscribe(url, sourceLine, sourceColumn,
|
||||
this._updateLocation);
|
||||
}
|
||||
},
|
||||
|
||||
get isSelectorEditable() {
|
||||
|
@ -110,13 +124,8 @@ RuleEditor.prototype = {
|
|||
this.source = createChild(this.element, "div", {
|
||||
class: "ruleview-rule-source theme-link"
|
||||
});
|
||||
this.source.addEventListener("click", () => {
|
||||
if (this.source.hasAttribute("unselectable")) {
|
||||
return;
|
||||
}
|
||||
let rule = this.rule.domRule;
|
||||
this.ruleView.emit("ruleview-linked-clicked", rule);
|
||||
});
|
||||
this.source.addEventListener("click", this._onSourceClick);
|
||||
|
||||
let sourceLabel = this.doc.createElement("span");
|
||||
sourceLabel.classList.add("ruleview-rule-source-label");
|
||||
this.source.appendChild(sourceLabel);
|
||||
|
@ -225,6 +234,23 @@ RuleEditor.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a tool is registered or unregistered.
|
||||
*/
|
||||
_onToolChanged: function () {
|
||||
// When the source editor is registered, update the source links
|
||||
// to be clickable; and if it is unregistered, update the links to
|
||||
// be unclickable. However, some links are never clickable, so
|
||||
// filter those out first.
|
||||
if (this.source.getAttribute("unselectable") === "permanent") {
|
||||
// Nothing.
|
||||
} else if (this.toolbox.isToolRegistered("styleeditor")) {
|
||||
this.source.removeAttribute("unselectable");
|
||||
} else {
|
||||
this.source.setAttribute("unselectable", "true");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler called when a property changes on the
|
||||
* StyleRuleActor.
|
||||
|
@ -233,22 +259,76 @@ RuleEditor.prototype = {
|
|||
this.updateSourceLink();
|
||||
},
|
||||
|
||||
_onSourceClick: function () {
|
||||
if (this.source.hasAttribute("unselectable") || !this._currentLocation) {
|
||||
return;
|
||||
}
|
||||
|
||||
let target = this.ruleView.inspector.target;
|
||||
if (Tools.styleEditor.isTargetSupported(target)) {
|
||||
gDevTools.showToolbox(target, "styleeditor").then(toolbox => {
|
||||
let {url, line, column} = this._currentLocation;
|
||||
toolbox.getCurrentPanel().selectStyleSheet(url, line, column);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the text of the source link to reflect whether we're showing
|
||||
* original sources or not. This is a callback for
|
||||
* SourceMapURLService.subscribe, which see.
|
||||
*
|
||||
* @param {Boolean} enabled
|
||||
* True if the passed-in location should be used; this means
|
||||
* that source mapping is in use and the remaining arguments
|
||||
* are the original location. False if the already-known
|
||||
* (stored) location should be used.
|
||||
* @param {String} url
|
||||
* The original URL
|
||||
* @param {Number} line
|
||||
* The original line number
|
||||
* @param {number} column
|
||||
* The original column number
|
||||
*/
|
||||
_updateLocation: function (enabled, url, line, column) {
|
||||
let displayURL = url;
|
||||
if (!enabled) {
|
||||
url = null;
|
||||
displayURL = null;
|
||||
if (this.rule.sheet) {
|
||||
url = this.rule.sheet.href || this.rule.sheet.nodeHref;
|
||||
displayURL = this.rule.sheet.href;
|
||||
}
|
||||
line = this.rule.ruleLine;
|
||||
column = this.rule.ruleColumn;
|
||||
}
|
||||
|
||||
this._currentLocation = {
|
||||
url,
|
||||
line,
|
||||
column
|
||||
};
|
||||
|
||||
let title = CssLogic.shortSource({href: displayURL});
|
||||
if (line > 0) {
|
||||
title += ":" + line;
|
||||
}
|
||||
if (this.rule.mediaText) {
|
||||
title += " @" + this.rule.mediaText;
|
||||
}
|
||||
|
||||
let sourceLabel = this.element.querySelector(".ruleview-rule-source-label");
|
||||
sourceLabel.setAttribute("title", title);
|
||||
sourceLabel.textContent = title;
|
||||
},
|
||||
|
||||
updateSourceLink: function () {
|
||||
if (this.rule.isSystem) {
|
||||
let sourceLabel = this.element.querySelector(".ruleview-rule-source-label");
|
||||
let title = this.rule.title;
|
||||
let sourceHref = (this.rule.sheet && this.rule.sheet.href) ?
|
||||
this.rule.sheet.href : title;
|
||||
let sourceLine = this.rule.ruleLine > 0 ? ":" + this.rule.ruleLine : "";
|
||||
|
||||
sourceLabel.setAttribute("title", sourceHref + sourceLine);
|
||||
|
||||
if (this.toolbox.isToolRegistered("styleeditor")) {
|
||||
this.source.removeAttribute("unselectable");
|
||||
} else {
|
||||
this.source.setAttribute("unselectable", true);
|
||||
}
|
||||
|
||||
if (this.rule.isSystem) {
|
||||
let uaLabel = STYLE_INSPECTOR_L10N.getStr("rule.userAgentStyles");
|
||||
sourceLabel.textContent = uaLabel + " " + title;
|
||||
|
||||
|
@ -256,36 +336,37 @@ RuleEditor.prototype = {
|
|||
// fly and the URI is not registered with the about: handler.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37
|
||||
if (sourceHref === "about:PreferenceStyleSheet") {
|
||||
this.source.setAttribute("unselectable", "true");
|
||||
this.source.setAttribute("unselectable", "permanent");
|
||||
sourceLabel.textContent = uaLabel;
|
||||
sourceLabel.removeAttribute("title");
|
||||
}
|
||||
} else {
|
||||
sourceLabel.textContent = title;
|
||||
if (this.rule.ruleLine === -1 && this.rule.domRule.parentStyleSheet) {
|
||||
this.source.setAttribute("unselectable", "true");
|
||||
}
|
||||
this._updateLocation(false);
|
||||
}
|
||||
|
||||
let showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||
if (showOrig && !this.rule.isSystem &&
|
||||
this.rule.domRule.type !== ELEMENT_STYLE) {
|
||||
// Only get the original source link if the right pref is set, if the rule
|
||||
// isn't a system rule and if it isn't an inline rule.
|
||||
this.rule.getOriginalSourceStrings().then((strings) => {
|
||||
sourceLabel.textContent = strings.short;
|
||||
sourceLabel.setAttribute("title", strings.full);
|
||||
}, console.error).then(() => {
|
||||
this.emit("source-link-updated");
|
||||
});
|
||||
let url = null;
|
||||
if (this.rule.sheet) {
|
||||
url = this.rule.sheet.href || this.rule.sheet.nodeHref;
|
||||
}
|
||||
if (url && !this.rule.isSystem && this.rule.domRule.type !== ELEMENT_STYLE) {
|
||||
// Only get the original source link if the rule isn't a system
|
||||
// rule and if it isn't an inline rule.
|
||||
let sourceLine = this.rule.ruleLine;
|
||||
let sourceColumn = this.rule.ruleColumn;
|
||||
this.sourceMapURLService.subscribe(url, sourceLine, sourceColumn,
|
||||
this._updateLocation);
|
||||
// Set "unselectable" appropriately.
|
||||
this._onToolChanged();
|
||||
} else if (this.rule.domRule.type === ELEMENT_STYLE) {
|
||||
this.source.setAttribute("unselectable", "permanent");
|
||||
} else {
|
||||
// If we're not getting the original source link, then we can emit the
|
||||
// event immediately (but still asynchronously to give consumers a chance
|
||||
// to register it after having instantiated the RuleEditor).
|
||||
// Set "unselectable" appropriately.
|
||||
this._onToolChanged();
|
||||
}
|
||||
|
||||
promise.resolve().then(() => {
|
||||
this.emit("source-link-updated");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -648,6 +648,10 @@ netmonitor.toolbar.disableCache.tooltip=Disable HTTP cache
|
|||
# in the network toolbar for the "Clear" button.
|
||||
netmonitor.toolbar.clear=Clear
|
||||
|
||||
# LOCALIZATION NOTE (netmonitor.toolbar.toggleRecording): This is the label displayed
|
||||
# in the network toolbar for the toggle recording button.
|
||||
netmonitor.toolbar.toggleRecording=Pause/Resume recording network log
|
||||
|
||||
# LOCALIZATION NOTE (netmonitor.toolbar.perf): This is the label displayed
|
||||
# in the network toolbar for the performance analysis button.
|
||||
netmonitor.toolbar.perf=Toggle performance analysis…
|
||||
|
|
|
@ -15,11 +15,6 @@
|
|||
- is displayed as the tooltip for the buffer capacity during a recording. -->
|
||||
<!ENTITY performanceUI.bufferStatusTooltip "The profiler stores samples in a circular buffer, and once the buffer reaches the limit for a recording, newer samples begin to overwrite samples at the beginning of the recording.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.disabledRealTime.nonE10SBuild): This string
|
||||
- is displayed as a message for why the real time overview graph is disabled
|
||||
- when running on a non-multiprocess build. -->
|
||||
<!ENTITY performanceUI.disabledRealTime.nonE10SBuild "Realtime recording data disabled on non-multiprocess Firefox.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.disabledRealTime.disabledE10S): This string
|
||||
- is displayed as a message for why the real time overview graph is disabled
|
||||
- when running on a build that can run multiprocess Firefox, but just is not enabled. -->
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
const { BrowserLoader } = Components.utils.import("resource://devtools/client/shared/browser-loader.js", {});
|
||||
const { BrowserLoader } = Components.utils.import(
|
||||
"resource://devtools/client/shared/browser-loader.js", {});
|
||||
const require = window.windowRequire = BrowserLoader({
|
||||
baseURI: "resource://devtools/client/netmonitor/",
|
||||
window,
|
||||
|
@ -25,15 +26,23 @@
|
|||
const { render, unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
|
||||
const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
|
||||
const { Connector } = require("./src/connector/index");
|
||||
const { configureStore } = require("./src/utils/create-store");
|
||||
const store = configureStore();
|
||||
const actions = bindActionCreators(require("./src/actions/index"), store.dispatch);
|
||||
const { onFirefoxConnect, onDisconnect } = require("./src/connector/index");
|
||||
const App = createFactory(require("./src/components/app"));
|
||||
const { getDisplayedRequestById } = require("./src/selectors/index");
|
||||
const { EVENTS } = require("./src/constants");
|
||||
|
||||
// Inject EventEmitter into global window.
|
||||
EventEmitter.decorate(window);
|
||||
|
||||
// Configure store/state object.
|
||||
let connector = new Connector();
|
||||
const store = configureStore(connector);
|
||||
const actions = bindActionCreators(require("./src/actions/index"), store.dispatch);
|
||||
|
||||
// Inject to global window for testing
|
||||
window.store = store;
|
||||
window.connector = connector;
|
||||
|
||||
window.Netmonitor = {
|
||||
bootstrap({ toolbox }) {
|
||||
|
@ -53,17 +62,57 @@
|
|||
top.openUILinkIn(link, "tab");
|
||||
};
|
||||
|
||||
const App = createFactory(require("./src/components/app"));
|
||||
// Render the root Application component.
|
||||
const sourceMapService = toolbox.sourceMapURLService;
|
||||
const app = App({ sourceMapService, openLink });
|
||||
const app = App({ connector, openLink, sourceMapService });
|
||||
render(Provider({ store }, app), this.mount);
|
||||
return onFirefoxConnect(connection, actions, store.getState);
|
||||
|
||||
// Connect to the Firefox backend by default.
|
||||
return connector.connectFirefox(connection, actions, store.getState);
|
||||
},
|
||||
|
||||
destroy() {
|
||||
unmountComponentAtNode(this.mount);
|
||||
return onDisconnect();
|
||||
return connector.disconnect();
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects the specified request in the waterfall and opens the details view.
|
||||
* This is a firefox toolbox specific API, which providing an ability to inspect
|
||||
* a network request directly from other internal toolbox panel.
|
||||
*
|
||||
* @param {string} requestId The actor ID of the request to inspect.
|
||||
* @return {object} A promise resolved once the task finishes.
|
||||
*/
|
||||
inspectRequest(requestId) {
|
||||
// Look for the request in the existing ones or wait for it to appear, if
|
||||
// the network monitor is still loading.
|
||||
return new Promise((resolve) => {
|
||||
let request = null;
|
||||
let inspector = () => {
|
||||
request = getDisplayedRequestById(store.getState(), requestId);
|
||||
if (!request) {
|
||||
// Reset filters so that the request is visible.
|
||||
actions.toggleRequestFilterType("all");
|
||||
request = getDisplayedRequestById(store.getState(), requestId);
|
||||
}
|
||||
|
||||
// If the request was found, select it. Otherwise this function will be
|
||||
// called again once new requests arrive.
|
||||
if (request) {
|
||||
window.off(EVENTS.REQUEST_ADDED, inspector);
|
||||
actions.selectRequest(request.id);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
inspector();
|
||||
|
||||
if (!request) {
|
||||
window.on(EVENTS.REQUEST_ADDED, inspector);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Implement support for:
|
||||
|
|
|
@ -13,7 +13,6 @@ const React = require("react");
|
|||
const ReactDOM = require("react-dom");
|
||||
const { bindActionCreators } = require("redux");
|
||||
const { bootstrap, renderRoot } = require("devtools-launchpad");
|
||||
const EventEmitter = require("devtools-modules/src/utils/event-emitter");
|
||||
const { Services: { appinfo, pref }} = require("devtools-modules");
|
||||
|
||||
// Initialize preferences as early as possible
|
||||
|
@ -39,15 +38,17 @@ pref("devtools.netmonitor.har.enableAutoExportToFile", false);
|
|||
pref("devtools.netmonitor.persistlog", false);
|
||||
pref("devtools.styleeditor.enabled", true);
|
||||
|
||||
const { configureStore } = require("./src/utils/create-store");
|
||||
|
||||
require("./src/assets/styles/netmonitor.css");
|
||||
|
||||
const EventEmitter = require("devtools-modules/src/utils/event-emitter");
|
||||
EventEmitter.decorate(window);
|
||||
|
||||
const { configureStore } = require("./src/utils/create-store");
|
||||
const App = require("./src/components/app");
|
||||
const store = configureStore();
|
||||
const { Connector } = require("./src/connector/index");
|
||||
const connector = new Connector();
|
||||
const store = configureStore(connector);
|
||||
const actions = bindActionCreators(require("./src/actions"), store.dispatch);
|
||||
const { onConnect } = require("./src/connector");
|
||||
|
||||
// Inject to global window for testing
|
||||
window.store = store;
|
||||
|
@ -80,6 +81,7 @@ bootstrap(React, ReactDOM).then((connection) => {
|
|||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
renderRoot(React, ReactDOM, App, store);
|
||||
onConnect(connection, actions, store.getState);
|
||||
|
||||
renderRoot(React, ReactDOM, App, store, {connector});
|
||||
connector.connect(connection, actions, store.getState);
|
||||
});
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { sendHTTPRequest } = require("../connector/index");
|
||||
const {
|
||||
ADD_REQUEST,
|
||||
CLEAR_REQUESTS,
|
||||
CLONE_SELECTED_REQUEST,
|
||||
REMOVE_SELECTED_CUSTOM_REQUEST,
|
||||
SEND_CUSTOM_REQUEST,
|
||||
TOGGLE_RECORDING,
|
||||
UPDATE_REQUEST,
|
||||
} = require("../constants");
|
||||
const { getSelectedRequest } = require("../selectors/index");
|
||||
|
@ -46,7 +46,7 @@ function cloneSelectedRequest() {
|
|||
/**
|
||||
* Send a new HTTP request using the data in the custom request form.
|
||||
*/
|
||||
function sendCustomRequest() {
|
||||
function sendCustomRequest(connector) {
|
||||
return (dispatch, getState) => {
|
||||
const selected = getSelectedRequest(getState());
|
||||
|
||||
|
@ -67,7 +67,7 @@ function sendCustomRequest() {
|
|||
data.body = selected.requestPostData.postData.text;
|
||||
}
|
||||
|
||||
sendHTTPRequest(data, (response) => {
|
||||
connector.sendHTTPRequest(data, (response) => {
|
||||
return dispatch({
|
||||
type: SEND_CUSTOM_REQUEST,
|
||||
id: response.eventActor.actor,
|
||||
|
@ -92,11 +92,21 @@ function clearRequests() {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle monitoring
|
||||
*/
|
||||
function toggleRecording() {
|
||||
return {
|
||||
type: TOGGLE_RECORDING
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addRequest,
|
||||
clearRequests,
|
||||
cloneSelectedRequest,
|
||||
removeSelectedCustomRequest,
|
||||
sendCustomRequest,
|
||||
toggleRecording,
|
||||
updateRequest,
|
||||
};
|
||||
|
|
|
@ -15,7 +15,6 @@ const {
|
|||
TOGGLE_COLUMN,
|
||||
WATERFALL_RESIZE,
|
||||
} = require("../constants");
|
||||
const { triggerActivity } = require("../connector/index");
|
||||
|
||||
/**
|
||||
* Change network details panel.
|
||||
|
@ -56,11 +55,12 @@ function disableBrowserCache(disabled) {
|
|||
/**
|
||||
* Change performance statistics panel open state.
|
||||
*
|
||||
* @param {Object} connector - connector object to the backend
|
||||
* @param {boolean} visible - expected performance statistics panel open state
|
||||
*/
|
||||
function openStatistics(open) {
|
||||
function openStatistics(connector, open) {
|
||||
if (open) {
|
||||
triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
|
||||
connector.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
|
||||
}
|
||||
return {
|
||||
type: OPEN_STATISTICS,
|
||||
|
@ -139,9 +139,9 @@ function toggleBrowserCache() {
|
|||
/**
|
||||
* Toggle performance statistics panel.
|
||||
*/
|
||||
function toggleStatistics() {
|
||||
function toggleStatistics(connector) {
|
||||
return (dispatch, getState) =>
|
||||
dispatch(openStatistics(!getState().ui.statisticsOpen));
|
||||
dispatch(openStatistics(connector, !getState().ui.statisticsOpen));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -50,6 +50,12 @@
|
|||
--sort-descending-image: url(chrome://devtools/skin/images/firebug/arrow-down.svg);
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
:root {
|
||||
--play-icon-url: url("chrome://devtools/skin/images/play.svg");
|
||||
--pause-icon-url: url("chrome://devtools/skin/images/pause.svg");
|
||||
}
|
||||
|
||||
/* General */
|
||||
|
||||
* {
|
||||
|
@ -97,6 +103,14 @@ body,
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.devtools-button.devtools-pause-icon::before {
|
||||
background-image: var(--pause-icon-url);
|
||||
}
|
||||
|
||||
.devtools-button.devtools-play-icon::before {
|
||||
background-image: var(--play-icon-url);
|
||||
}
|
||||
|
||||
/* Learn more links */
|
||||
|
||||
.learn-more-link::before {
|
||||
|
|
|
@ -17,18 +17,25 @@ const StatisticsPanel = createFactory(require("./statistics-panel"));
|
|||
|
||||
const { div } = DOM;
|
||||
|
||||
/*
|
||||
/**
|
||||
* App component
|
||||
* The top level component for representing main panel
|
||||
*/
|
||||
function App({
|
||||
connector,
|
||||
openLink,
|
||||
sourceMapService,
|
||||
statisticsOpen,
|
||||
}) {
|
||||
return (
|
||||
div({ className: "network-monitor" },
|
||||
!statisticsOpen ? MonitorPanel({ openLink, sourceMapService }) : StatisticsPanel()
|
||||
!statisticsOpen ? MonitorPanel({
|
||||
connector,
|
||||
sourceMapService,
|
||||
openLink,
|
||||
}) : StatisticsPanel({
|
||||
connector
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -36,9 +43,13 @@ function App({
|
|||
App.displayName = "App";
|
||||
|
||||
App.propTypes = {
|
||||
// The backend connector object.
|
||||
connector: PropTypes.object.isRequired,
|
||||
// Callback for opening links in the UI
|
||||
openLink: PropTypes.func,
|
||||
// Service to enable the source map feature.
|
||||
sourceMapService: PropTypes.object,
|
||||
// True if the stats panel is opened.
|
||||
statisticsOpen: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -148,6 +148,7 @@ function CustomRequestPanel({
|
|||
CustomRequestPanel.displayName = "CustomRequestPanel";
|
||||
|
||||
CustomRequestPanel.propTypes = {
|
||||
connector: PropTypes.object.isRequired,
|
||||
removeSelectedCustomRequest: PropTypes.func.isRequired,
|
||||
request: PropTypes.object,
|
||||
sendCustomRequest: PropTypes.func.isRequired,
|
||||
|
@ -249,9 +250,9 @@ function updateCustomRequestFields(evt, request, updateRequest) {
|
|||
|
||||
module.exports = connect(
|
||||
(state) => ({ request: getSelectedRequest(state) }),
|
||||
(dispatch) => ({
|
||||
(dispatch, props) => ({
|
||||
removeSelectedCustomRequest: () => dispatch(Actions.removeSelectedCustomRequest()),
|
||||
sendCustomRequest: () => dispatch(Actions.sendCustomRequest()),
|
||||
sendCustomRequest: () => dispatch(Actions.sendCustomRequest(props.connector)),
|
||||
updateRequest: (id, data, batch) => dispatch(Actions.updateRequest(id, data, batch)),
|
||||
})
|
||||
)(CustomRequestPanel);
|
||||
|
|
|
@ -14,7 +14,6 @@ const {
|
|||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
const Actions = require("../actions/index");
|
||||
const { getLongString } = require("../connector/index");
|
||||
const { getFormDataSections } = require("../utils/request-utils");
|
||||
const { getSelectedRequest } = require("../selectors/index");
|
||||
|
||||
|
@ -26,7 +25,7 @@ const Toolbar = createFactory(require("./toolbar"));
|
|||
const { div } = DOM;
|
||||
const MediaQueryList = window.matchMedia("(min-width: 700px)");
|
||||
|
||||
/*
|
||||
/**
|
||||
* Monitor panel component
|
||||
* The main panel for displaying various network request information
|
||||
*/
|
||||
|
@ -34,11 +33,11 @@ const MonitorPanel = createClass({
|
|||
displayName: "MonitorPanel",
|
||||
|
||||
propTypes: {
|
||||
connector: PropTypes.object.isRequired,
|
||||
isEmpty: PropTypes.bool.isRequired,
|
||||
networkDetailsOpen: PropTypes.bool.isRequired,
|
||||
openNetworkDetails: PropTypes.func.isRequired,
|
||||
request: PropTypes.object,
|
||||
// Service to enable the source map feature.
|
||||
sourceMapService: PropTypes.object,
|
||||
openLink: PropTypes.func,
|
||||
updateRequest: PropTypes.func.isRequired,
|
||||
|
@ -72,7 +71,7 @@ const MonitorPanel = createClass({
|
|||
requestHeaders,
|
||||
requestHeadersFromUploadStream,
|
||||
requestPostData,
|
||||
getLongString,
|
||||
this.props.connector.getLongString,
|
||||
).then((newFormDataSections) => {
|
||||
updateRequest(
|
||||
request.id,
|
||||
|
@ -106,6 +105,7 @@ const MonitorPanel = createClass({
|
|||
|
||||
render() {
|
||||
let {
|
||||
connector,
|
||||
isEmpty,
|
||||
networkDetailsOpen,
|
||||
sourceMapService,
|
||||
|
@ -126,9 +126,10 @@ const MonitorPanel = createClass({
|
|||
minSize: "50px",
|
||||
maxSize: "80%",
|
||||
splitterSize: "1px",
|
||||
startPanel: RequestList({ isEmpty }),
|
||||
startPanel: RequestList({ isEmpty, connector }),
|
||||
endPanel: networkDetailsOpen && NetworkDetailsPanel({
|
||||
ref: "endPanel",
|
||||
connector,
|
||||
sourceMapService,
|
||||
openLink,
|
||||
}),
|
||||
|
|
|
@ -19,10 +19,11 @@ const TabboxPanel = createFactory(require("./tabbox-panel"));
|
|||
|
||||
const { div } = DOM;
|
||||
|
||||
/*
|
||||
/**
|
||||
* Network details panel component
|
||||
*/
|
||||
function NetworkDetailsPanel({
|
||||
connector,
|
||||
activeTabId,
|
||||
cloneSelectedRequest,
|
||||
request,
|
||||
|
@ -40,12 +41,14 @@ function NetworkDetailsPanel({
|
|||
TabboxPanel({
|
||||
activeTabId,
|
||||
cloneSelectedRequest,
|
||||
connector,
|
||||
openLink,
|
||||
request,
|
||||
selectTab,
|
||||
sourceMapService,
|
||||
openLink,
|
||||
}) :
|
||||
CustomRequestPanel({
|
||||
connector,
|
||||
request,
|
||||
})
|
||||
)
|
||||
|
@ -55,12 +58,12 @@ function NetworkDetailsPanel({
|
|||
NetworkDetailsPanel.displayName = "NetworkDetailsPanel";
|
||||
|
||||
NetworkDetailsPanel.propTypes = {
|
||||
connector: PropTypes.object.isRequired,
|
||||
activeTabId: PropTypes.string,
|
||||
cloneSelectedRequest: PropTypes.func.isRequired,
|
||||
open: PropTypes.bool,
|
||||
request: PropTypes.object,
|
||||
selectTab: PropTypes.func.isRequired,
|
||||
// Service to enable the source map feature.
|
||||
sourceMapService: PropTypes.object,
|
||||
openLink: PropTypes.func,
|
||||
};
|
||||
|
|
|
@ -35,6 +35,7 @@ const RequestListContent = createClass({
|
|||
displayName: "RequestListContent",
|
||||
|
||||
propTypes: {
|
||||
connector: PropTypes.object.isRequired,
|
||||
columns: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
displayedRequests: PropTypes.object.isRequired,
|
||||
|
@ -51,10 +52,12 @@ const RequestListContent = createClass({
|
|||
},
|
||||
|
||||
componentWillMount() {
|
||||
const { dispatch } = this.props;
|
||||
const { dispatch, connector } = this.props;
|
||||
this.contextMenu = new RequestListContextMenu({
|
||||
cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
|
||||
openStatistics: (open) => dispatch(Actions.openStatistics(open)),
|
||||
getTabTarget: connector.getTabTarget,
|
||||
getLongString: connector.getLongString,
|
||||
openStatistics: (open) => dispatch(Actions.openStatistics(connector, open)),
|
||||
});
|
||||
this.tooltip = new HTMLTooltip(window.parent.document, { type: "arrow" });
|
||||
},
|
||||
|
@ -156,8 +159,9 @@ const RequestListContent = createClass({
|
|||
return false;
|
||||
}
|
||||
|
||||
let { connector } = this.props;
|
||||
if (requestItem.responseContent && target.closest(".requests-list-icon")) {
|
||||
return setTooltipImageContent(tooltip, itemEl, requestItem);
|
||||
return setTooltipImageContent(connector, tooltip, itemEl, requestItem);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -12,7 +12,6 @@ const {
|
|||
} = require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const Actions = require("../actions/index");
|
||||
const { triggerActivity } = require("../connector/index");
|
||||
const { ACTIVITY_TYPE } = require("../constants");
|
||||
const { L10N } = require("../utils/l10n");
|
||||
const { getPerformanceAnalysisURL } = require("../utils/mdn-utils");
|
||||
|
@ -37,6 +36,7 @@ const RequestListEmptyNotice = createClass({
|
|||
displayName: "RequestListEmptyNotice",
|
||||
|
||||
propTypes: {
|
||||
connector: PropTypes.object.isRequired,
|
||||
onReloadClick: PropTypes.func.isRequired,
|
||||
onPerfClick: PropTypes.func.isRequired,
|
||||
},
|
||||
|
@ -75,8 +75,9 @@ const RequestListEmptyNotice = createClass({
|
|||
|
||||
module.exports = connect(
|
||||
undefined,
|
||||
dispatch => ({
|
||||
onPerfClick: () => dispatch(Actions.openStatistics(true)),
|
||||
onReloadClick: () => triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT),
|
||||
(dispatch, props) => ({
|
||||
onPerfClick: () => dispatch(Actions.openStatistics(props.connector, true)),
|
||||
onReloadClick: () => props.connector.triggerActivity(
|
||||
ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT),
|
||||
})
|
||||
)(RequestListEmptyNotice);
|
||||
|
|
|
@ -21,12 +21,15 @@ const { div } = DOM;
|
|||
/**
|
||||
* Request panel component
|
||||
*/
|
||||
function RequestList({ isEmpty }) {
|
||||
function RequestList({
|
||||
connector,
|
||||
isEmpty,
|
||||
}) {
|
||||
return (
|
||||
div({ className: "request-list-container" },
|
||||
RequestListHeader(),
|
||||
isEmpty ? RequestListEmptyNotice() : RequestListContent(),
|
||||
StatusBar(),
|
||||
isEmpty ? RequestListEmptyNotice({connector}) : RequestListContent({connector}),
|
||||
StatusBar({connector}),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -34,6 +37,7 @@ function RequestList({ isEmpty }) {
|
|||
RequestList.displayName = "RequestList";
|
||||
|
||||
RequestList.propTypes = {
|
||||
connector: PropTypes.object.isRequired,
|
||||
isEmpty: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -9,14 +9,18 @@ const {
|
|||
DOM,
|
||||
PropTypes,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { viewSourceInDebugger } = require("../connector/index");
|
||||
|
||||
const { div } = DOM;
|
||||
|
||||
// Components
|
||||
const StackTrace = createFactory(require("devtools/client/shared/components/StackTrace"));
|
||||
|
||||
/**
|
||||
* This component represents a side panel responsible for
|
||||
* rendering stack-trace info for selected request.
|
||||
*/
|
||||
function StackTracePanel({
|
||||
connector,
|
||||
openLink,
|
||||
request,
|
||||
sourceMapService,
|
||||
|
@ -27,7 +31,9 @@ function StackTracePanel({
|
|||
div({ className: "panel-container" },
|
||||
StackTrace({
|
||||
stacktrace,
|
||||
onViewSourceInDebugger: ({ url, line }) => viewSourceInDebugger(url, line),
|
||||
onViewSourceInDebugger: ({ url, line }) => {
|
||||
return connector.viewSourceInDebugger(url, line);
|
||||
},
|
||||
sourceMapService,
|
||||
openLink,
|
||||
}),
|
||||
|
@ -38,8 +44,8 @@ function StackTracePanel({
|
|||
StackTracePanel.displayName = "StackTracePanel";
|
||||
|
||||
StackTracePanel.propTypes = {
|
||||
connector: PropTypes.object.isRequired,
|
||||
request: PropTypes.object.isRequired,
|
||||
// Service to enable the source map feature.
|
||||
sourceMapService: PropTypes.object,
|
||||
openLink: PropTypes.func,
|
||||
};
|
||||
|
|
|
@ -44,6 +44,7 @@ const StatisticsPanel = createClass({
|
|||
displayName: "StatisticsPanel",
|
||||
|
||||
propTypes: {
|
||||
connector: PropTypes.object.isRequired,
|
||||
closeStatistics: PropTypes.func.isRequired,
|
||||
enableRequestFilterTypeOnly: PropTypes.func.isRequired,
|
||||
requests: PropTypes.object,
|
||||
|
@ -302,8 +303,8 @@ module.exports = connect(
|
|||
(state) => ({
|
||||
requests: state.requests.requests.valueSeq(),
|
||||
}),
|
||||
(dispatch) => ({
|
||||
closeStatistics: () => dispatch(Actions.openStatistics(false)),
|
||||
(dispatch, props) => ({
|
||||
closeStatistics: () => dispatch(Actions.openStatistics(props.connector, false)),
|
||||
enableRequestFilterTypeOnly: (label) =>
|
||||
dispatch(Actions.enableRequestFilterTypeOnly(label)),
|
||||
})
|
||||
|
|
|
@ -88,6 +88,7 @@ function StatusBar({ summary, openStatistics, timingMarkers }) {
|
|||
StatusBar.displayName = "StatusBar";
|
||||
|
||||
StatusBar.propTypes = {
|
||||
connector: PropTypes.object.isRequired,
|
||||
openStatistics: PropTypes.func.isRequired,
|
||||
summary: PropTypes.object.isRequired,
|
||||
timingMarkers: PropTypes.object.isRequired,
|
||||
|
@ -102,7 +103,7 @@ module.exports = connect(
|
|||
load: getDisplayedTimingMarker(state, "firstDocumentLoadTimestamp"),
|
||||
},
|
||||
}),
|
||||
(dispatch) => ({
|
||||
openStatistics: () => dispatch(Actions.openStatistics(true)),
|
||||
(dispatch, props) => ({
|
||||
openStatistics: () => dispatch(Actions.openStatistics(props.connector, true)),
|
||||
}),
|
||||
)(StatusBar);
|
||||
|
|
|
@ -31,17 +31,18 @@ const SECURITY_TITLE = L10N.getStr("netmonitor.tab.security");
|
|||
const STACK_TRACE_TITLE = L10N.getStr("netmonitor.tab.stackTrace");
|
||||
const TIMINGS_TITLE = L10N.getStr("netmonitor.tab.timings");
|
||||
|
||||
/*
|
||||
/**
|
||||
* Tabbox panel component
|
||||
* Display the network request details
|
||||
*/
|
||||
function TabboxPanel({
|
||||
activeTabId,
|
||||
cloneSelectedRequest = ()=>{},
|
||||
cloneSelectedRequest = () => {},
|
||||
connector,
|
||||
openLink,
|
||||
request,
|
||||
selectTab,
|
||||
sourceMapService,
|
||||
openLink,
|
||||
}) {
|
||||
if (!request) {
|
||||
return null;
|
||||
|
@ -90,7 +91,7 @@ function TabboxPanel({
|
|||
id: PANELS.STACK_TRACE,
|
||||
title: STACK_TRACE_TITLE,
|
||||
},
|
||||
StackTracePanel({ request, sourceMapService, openLink }),
|
||||
StackTracePanel({ request, sourceMapService, openLink, connector }),
|
||||
),
|
||||
request.securityState && request.securityState !== "insecure" &&
|
||||
TabPanel({
|
||||
|
@ -108,11 +109,11 @@ TabboxPanel.displayName = "TabboxPanel";
|
|||
TabboxPanel.propTypes = {
|
||||
activeTabId: PropTypes.string,
|
||||
cloneSelectedRequest: PropTypes.func,
|
||||
connector: PropTypes.object.isRequired,
|
||||
openLink: PropTypes.func,
|
||||
request: PropTypes.object,
|
||||
selectTab: PropTypes.func.isRequired,
|
||||
// Service to enable the source map feature.
|
||||
sourceMapService: PropTypes.object,
|
||||
openLink: PropTypes.func,
|
||||
};
|
||||
|
||||
module.exports = connect()(TabboxPanel);
|
||||
|
|
|
@ -16,6 +16,7 @@ const Actions = require("../actions/index");
|
|||
const { FILTER_SEARCH_DELAY, FILTER_TAGS } = require("../constants");
|
||||
const {
|
||||
getDisplayedRequestsSummary,
|
||||
getRecordingState,
|
||||
getRequestFilterTypes,
|
||||
getTypeFilteredRequests,
|
||||
isNetworkDetailsToggleButtonDisabled,
|
||||
|
@ -29,12 +30,15 @@ const SearchBox = createFactory(require("devtools/client/shared/components/Searc
|
|||
|
||||
const { button, div, input, label, span } = DOM;
|
||||
|
||||
const COLLPASE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
|
||||
// Localization
|
||||
const COLLAPSE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
|
||||
const EXPAND_DETAILS_PANE = L10N.getStr("expandDetailsPane");
|
||||
const SEARCH_KEY_SHORTCUT = L10N.getStr("netmonitor.toolbar.filterFreetext.key");
|
||||
const SEARCH_PLACE_HOLDER = L10N.getStr("netmonitor.toolbar.filterFreetext.label");
|
||||
const TOOLBAR_CLEAR = L10N.getStr("netmonitor.toolbar.clear");
|
||||
const TOOLBAR_TOGGLE_RECORDING = L10N.getStr("netmonitor.toolbar.toggleRecording");
|
||||
|
||||
// Preferences
|
||||
const DEVTOOLS_DISABLE_CACHE_PREF = "devtools.cache.disabled";
|
||||
const DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF = "devtools.netmonitor.persistlog";
|
||||
const TOOLBAR_FILTER_LABELS = FILTER_TAGS.concat("all").reduce((o, tag) =>
|
||||
|
@ -46,14 +50,18 @@ const ENABLE_PERSISTENT_LOGS_LABEL =
|
|||
const DISABLE_CACHE_TOOLTIP = L10N.getStr("netmonitor.toolbar.disableCache.tooltip");
|
||||
const DISABLE_CACHE_LABEL = L10N.getStr("netmonitor.toolbar.disableCache.label");
|
||||
|
||||
/*
|
||||
* Network monitor toolbar component
|
||||
/**
|
||||
* Network monitor toolbar component.
|
||||
*
|
||||
* Toolbar contains a set of useful tools to control network requests
|
||||
* as well as set of filters for filtering the content.
|
||||
*/
|
||||
const Toolbar = createClass({
|
||||
displayName: "Toolbar",
|
||||
|
||||
propTypes: {
|
||||
toggleRecording: PropTypes.func.isRequired,
|
||||
recording: PropTypes.bool.isRequired,
|
||||
clearRequests: PropTypes.func.isRequired,
|
||||
requestFilterTypes: PropTypes.array.isRequired,
|
||||
setRequestFilterText: PropTypes.func.isRequired,
|
||||
|
@ -70,8 +78,40 @@ const Toolbar = createClass({
|
|||
filteredRequests: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
Services.prefs.addObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
|
||||
this.updatePersistentLogsEnabled);
|
||||
Services.prefs.addObserver(DEVTOOLS_DISABLE_CACHE_PREF,
|
||||
this.updateBrowserCacheDisabled);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
Services.prefs.removeObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
|
||||
this.updatePersistentLogsEnabled);
|
||||
Services.prefs.removeObserver(DEVTOOLS_DISABLE_CACHE_PREF,
|
||||
this.updateBrowserCacheDisabled);
|
||||
},
|
||||
|
||||
toggleRequestFilterType(evt) {
|
||||
if (evt.type === "keydown" && (evt.key !== "" || evt.key !== "Enter")) {
|
||||
return;
|
||||
}
|
||||
this.props.toggleRequestFilterType(evt.target.dataset.key);
|
||||
},
|
||||
|
||||
updatePersistentLogsEnabled() {
|
||||
this.props.enablePersistentLogs(
|
||||
Services.prefs.getBoolPref(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF));
|
||||
},
|
||||
|
||||
updateBrowserCacheDisabled() {
|
||||
this.props.disableBrowserCache(
|
||||
Services.prefs.getBoolPref(DEVTOOLS_DISABLE_CACHE_PREF));
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
toggleRecording,
|
||||
clearRequests,
|
||||
requestFilterTypes,
|
||||
setRequestFilterText,
|
||||
|
@ -83,16 +123,19 @@ const Toolbar = createClass({
|
|||
toggleBrowserCache,
|
||||
browserCacheDisabled,
|
||||
filteredRequests,
|
||||
recording,
|
||||
} = this.props;
|
||||
|
||||
let toggleButtonClassName = [
|
||||
"network-details-panel-toggle",
|
||||
"devtools-button",
|
||||
];
|
||||
|
||||
if (!networkDetailsOpen) {
|
||||
toggleButtonClassName.push("pane-collapsed");
|
||||
}
|
||||
|
||||
// Render list of filter-buttons.
|
||||
let buttons = requestFilterTypes.map(([type, checked]) => {
|
||||
let classList = ["devtools-button", `requests-list-filter-${type}-button`];
|
||||
checked && classList.push("checked");
|
||||
|
@ -111,9 +154,23 @@ const Toolbar = createClass({
|
|||
);
|
||||
});
|
||||
|
||||
// Calculate class-list for toggle recording button. The button
|
||||
// has two states: pause/play.
|
||||
let toggleButtonClassList = [
|
||||
"devtools-button",
|
||||
"requests-list-pause-button",
|
||||
recording ? "devtools-pause-icon" : "devtools-play-icon",
|
||||
];
|
||||
|
||||
// Render the entire toolbar.
|
||||
return (
|
||||
span({ className: "devtools-toolbar devtools-toolbar-container" },
|
||||
span({ className: "devtools-toolbar-group" },
|
||||
button({
|
||||
className: toggleButtonClassList.join(" "),
|
||||
title: TOOLBAR_TOGGLE_RECORDING,
|
||||
onClick: toggleRecording,
|
||||
}),
|
||||
button({
|
||||
className: "devtools-button devtools-clear-icon requests-list-clear-button",
|
||||
title: TOOLBAR_CLEAR,
|
||||
|
@ -161,7 +218,7 @@ const Toolbar = createClass({
|
|||
}),
|
||||
button({
|
||||
className: toggleButtonClassName.join(" "),
|
||||
title: networkDetailsOpen ? COLLPASE_DETAILS_PANE : EXPAND_DETAILS_PANE,
|
||||
title: networkDetailsOpen ? COLLAPSE_DETAILS_PANE : EXPAND_DETAILS_PANE,
|
||||
disabled: networkDetailsToggleDisabled,
|
||||
tabIndex: "0",
|
||||
onClick: toggleNetworkDetails,
|
||||
|
@ -170,37 +227,6 @@ const Toolbar = createClass({
|
|||
)
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
Services.prefs.addObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
|
||||
this.updatePersistentLogsEnabled);
|
||||
Services.prefs.addObserver(DEVTOOLS_DISABLE_CACHE_PREF,
|
||||
this.updateBrowserCacheDisabled);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
Services.prefs.removeObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
|
||||
this.updatePersistentLogsEnabled);
|
||||
Services.prefs.removeObserver(DEVTOOLS_DISABLE_CACHE_PREF,
|
||||
this.updateBrowserCacheDisabled);
|
||||
},
|
||||
|
||||
toggleRequestFilterType(evt) {
|
||||
if (evt.type === "keydown" && (evt.key !== "" || evt.key !== "Enter")) {
|
||||
return;
|
||||
}
|
||||
this.props.toggleRequestFilterType(evt.target.dataset.key);
|
||||
},
|
||||
|
||||
updatePersistentLogsEnabled() {
|
||||
this.props.enablePersistentLogs(
|
||||
Services.prefs.getBoolPref(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF));
|
||||
},
|
||||
|
||||
updateBrowserCacheDisabled() {
|
||||
this.props.disableBrowserCache(
|
||||
Services.prefs.getBoolPref(DEVTOOLS_DISABLE_CACHE_PREF));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
|
@ -209,18 +235,20 @@ module.exports = connect(
|
|||
networkDetailsOpen: state.ui.networkDetailsOpen,
|
||||
persistentLogsEnabled: state.ui.persistentLogsEnabled,
|
||||
browserCacheDisabled: state.ui.browserCacheDisabled,
|
||||
recording: getRecordingState(state),
|
||||
requestFilterTypes: getRequestFilterTypes(state),
|
||||
filteredRequests: getTypeFilteredRequests(state),
|
||||
summary: getDisplayedRequestsSummary(state),
|
||||
}),
|
||||
(dispatch) => ({
|
||||
clearRequests: () => dispatch(Actions.clearRequests()),
|
||||
setRequestFilterText: (text) => dispatch(Actions.setRequestFilterText(text)),
|
||||
toggleRequestFilterType: (type) => dispatch(Actions.toggleRequestFilterType(type)),
|
||||
toggleNetworkDetails: () => dispatch(Actions.toggleNetworkDetails()),
|
||||
enablePersistentLogs: (enabled) => dispatch(Actions.enablePersistentLogs(enabled)),
|
||||
togglePersistentLogs: () => dispatch(Actions.togglePersistentLogs()),
|
||||
disableBrowserCache: (disabled) => dispatch(Actions.disableBrowserCache(disabled)),
|
||||
enablePersistentLogs: (enabled) => dispatch(Actions.enablePersistentLogs(enabled)),
|
||||
setRequestFilterText: (text) => dispatch(Actions.setRequestFilterText(text)),
|
||||
toggleBrowserCache: () => dispatch(Actions.toggleBrowserCache()),
|
||||
toggleNetworkDetails: () => dispatch(Actions.toggleNetworkDetails()),
|
||||
toggleRecording: () => dispatch(Actions.toggleRecording()),
|
||||
togglePersistentLogs: () => dispatch(Actions.togglePersistentLogs()),
|
||||
toggleRequestFilterType: (type) => dispatch(Actions.toggleRequestFilterType(type)),
|
||||
}),
|
||||
)(Toolbar);
|
||||
|
|
|
@ -35,6 +35,14 @@ class ChromeConnector {
|
|||
this.connector.disconnect();
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.disconnect();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* currently all events are about "navigation" is not support on CDP
|
||||
*/
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
const Services = require("Services");
|
||||
const { TimelineFront } = require("devtools/shared/fronts/timeline");
|
||||
const { ACTIVITY_TYPE, EVENTS } = require("../constants");
|
||||
const { getDisplayedRequestById } = require("../selectors/index");
|
||||
const FirefoxDataProvider = require("./firefox-data-provider");
|
||||
|
||||
class FirefoxConnector {
|
||||
|
@ -21,7 +20,6 @@ class FirefoxConnector {
|
|||
this.sendHTTPRequest = this.sendHTTPRequest.bind(this);
|
||||
this.setPreferences = this.setPreferences.bind(this);
|
||||
this.triggerActivity = this.triggerActivity.bind(this);
|
||||
this.inspectRequest = this.inspectRequest.bind(this);
|
||||
this.getTabTarget = this.getTabTarget.bind(this);
|
||||
this.viewSourceInDebugger = this.viewSourceInDebugger.bind(this);
|
||||
|
||||
|
@ -43,12 +41,7 @@ class FirefoxConnector {
|
|||
actions: this.actions,
|
||||
});
|
||||
|
||||
this.tabTarget.on("will-navigate", this.willNavigate);
|
||||
this.tabTarget.on("close", this.disconnect);
|
||||
this.webConsoleClient.on("networkEvent",
|
||||
this.dataProvider.onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate",
|
||||
this.dataProvider.onNetworkEventUpdate);
|
||||
this.addListeners();
|
||||
|
||||
// Don't start up waiting for timeline markers if the server isn't
|
||||
// recent enough to emit the markers we're interested in.
|
||||
|
@ -71,16 +64,38 @@ class FirefoxConnector {
|
|||
await this.timelineFront.destroy();
|
||||
}
|
||||
|
||||
this.tabTarget.off("will-navigate");
|
||||
this.tabTarget.off("close");
|
||||
this.removeListeners();
|
||||
|
||||
this.tabTarget = null;
|
||||
this.webConsoleClient.off("networkEvent");
|
||||
this.webConsoleClient.off("networkEventUpdate");
|
||||
this.webConsoleClient = null;
|
||||
this.timelineFront = null;
|
||||
this.dataProvider = null;
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.removeListeners();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
this.tabTarget.on("will-navigate", this.willNavigate);
|
||||
this.tabTarget.on("close", this.disconnect);
|
||||
this.webConsoleClient.on("networkEvent",
|
||||
this.dataProvider.onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate",
|
||||
this.dataProvider.onNetworkEventUpdate);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
this.tabTarget.off("will-navigate");
|
||||
this.tabTarget.off("close");
|
||||
this.webConsoleClient.off("networkEvent");
|
||||
this.webConsoleClient.off("networkEventUpdate");
|
||||
}
|
||||
|
||||
willNavigate() {
|
||||
if (!Services.prefs.getBoolPref("devtools.netmonitor.persistlog")) {
|
||||
this.actions.batchReset();
|
||||
|
@ -214,42 +229,6 @@ class FirefoxConnector {
|
|||
return Promise.reject(new Error("Invalid activity type"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the specified request in the waterfall and opens the details view.
|
||||
*
|
||||
* @param {string} requestId The actor ID of the request to inspect.
|
||||
* @return {object} A promise resolved once the task finishes.
|
||||
*/
|
||||
inspectRequest(requestId) {
|
||||
// Look for the request in the existing ones or wait for it to appear, if
|
||||
// the network monitor is still loading.
|
||||
return new Promise((resolve) => {
|
||||
let request = null;
|
||||
let inspector = () => {
|
||||
request = getDisplayedRequestById(this.getState(), requestId);
|
||||
if (!request) {
|
||||
// Reset filters so that the request is visible.
|
||||
this.actions.toggleRequestFilterType("all");
|
||||
request = getDisplayedRequestById(this.getState(), requestId);
|
||||
}
|
||||
|
||||
// If the request was found, select it. Otherwise this function will be
|
||||
// called again once new requests arrive.
|
||||
if (request) {
|
||||
window.off(EVENTS.REQUEST_ADDED, inspector);
|
||||
this.actions.selectRequest(request.id);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
inspector();
|
||||
|
||||
if (!request) {
|
||||
window.on(EVENTS.REQUEST_ADDED, inspector);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the network information packet from actor server
|
||||
*
|
||||
|
|
|
@ -428,6 +428,8 @@ class FirefoxDataProvider {
|
|||
});
|
||||
break;
|
||||
}
|
||||
|
||||
emit(EVENTS.NETWORK_EVENT_UPDATED, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,9 +3,33 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
let connector = {};
|
||||
|
||||
function onConnect(connection, actions, getState) {
|
||||
/**
|
||||
* Generic connector wrapper object that is responsible for
|
||||
* instantiating specific connector implementation according
|
||||
* to the client type.
|
||||
*/
|
||||
class Connector {
|
||||
constructor() {
|
||||
this.connector = null;
|
||||
|
||||
// Bind public API
|
||||
this.connect = this.connect.bind(this);
|
||||
this.disconnect = this.disconnect.bind(this);
|
||||
this.connectChrome = this.connectChrome.bind(this);
|
||||
this.connectFirefox = this.connectFirefox.bind(this);
|
||||
this.getLongString = this.getLongString.bind(this);
|
||||
this.getNetworkRequest = this.getNetworkRequest.bind(this);
|
||||
this.getTabTarget = this.getTabTarget.bind(this);
|
||||
this.sendHTTPRequest = this.sendHTTPRequest.bind(this);
|
||||
this.setPreferences = this.setPreferences.bind(this);
|
||||
this.triggerActivity = this.triggerActivity.bind(this);
|
||||
this.viewSourceInDebugger = this.viewSourceInDebugger.bind(this);
|
||||
}
|
||||
|
||||
// Connect/Disconnect API
|
||||
|
||||
connect(connection, actions, getState) {
|
||||
if (!connection || !connection.tab) {
|
||||
return;
|
||||
}
|
||||
|
@ -13,73 +37,67 @@ function onConnect(connection, actions, getState) {
|
|||
let { clientType } = connection.tab;
|
||||
switch (clientType) {
|
||||
case "chrome":
|
||||
onChromeConnect(connection, actions, getState);
|
||||
this.connectChrome(connection, actions, getState);
|
||||
break;
|
||||
case "firefox":
|
||||
onFirefoxConnect(connection, actions, getState);
|
||||
this.connectFirefox(connection, actions, getState);
|
||||
break;
|
||||
default:
|
||||
throw Error(`Unknown client type - ${clientType}`);
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.connector && this.connector.disconnect();
|
||||
}
|
||||
|
||||
connectChrome(connection, actions, getState) {
|
||||
this.connector = require("./chrome-connector");
|
||||
this.connector.connect(connection, actions, getState);
|
||||
}
|
||||
|
||||
connectFirefox(connection, actions, getState) {
|
||||
this.connector = require("./firefox-connector");
|
||||
this.connector.connect(connection, actions, getState);
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.connector.pause();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.connector.resume();
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
||||
getLongString() {
|
||||
return this.connector.getLongString(...arguments);
|
||||
}
|
||||
|
||||
getNetworkRequest() {
|
||||
return this.connector.getNetworkRequest(...arguments);
|
||||
}
|
||||
|
||||
getTabTarget() {
|
||||
return this.connector.getTabTarget();
|
||||
}
|
||||
|
||||
sendHTTPRequest() {
|
||||
return this.connector.sendHTTPRequest(...arguments);
|
||||
}
|
||||
|
||||
setPreferences() {
|
||||
return this.connector.setPreferences(...arguments);
|
||||
}
|
||||
|
||||
triggerActivity() {
|
||||
return this.connector.triggerActivity(...arguments);
|
||||
}
|
||||
|
||||
viewSourceInDebugger() {
|
||||
return this.connector.viewSourceInDebugger(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
function onDisconnect() {
|
||||
connector && connector.disconnect();
|
||||
}
|
||||
|
||||
function onChromeConnect(connection, actions, getState) {
|
||||
connector = require("./chrome-connector");
|
||||
connector.connect(connection, actions, getState);
|
||||
}
|
||||
|
||||
function onFirefoxConnect(connection, actions, getState) {
|
||||
connector = require("./firefox-connector");
|
||||
connector.connect(connection, actions, getState);
|
||||
}
|
||||
|
||||
function inspectRequest() {
|
||||
return connector.inspectRequest(...arguments);
|
||||
}
|
||||
|
||||
function getLongString() {
|
||||
return connector.getLongString(...arguments);
|
||||
}
|
||||
|
||||
function getNetworkRequest() {
|
||||
return connector.getNetworkRequest(...arguments);
|
||||
}
|
||||
|
||||
function getTabTarget() {
|
||||
return connector.getTabTarget();
|
||||
}
|
||||
|
||||
function sendHTTPRequest() {
|
||||
return connector.sendHTTPRequest(...arguments);
|
||||
}
|
||||
|
||||
function setPreferences() {
|
||||
return connector.setPreferences(...arguments);
|
||||
}
|
||||
|
||||
function triggerActivity() {
|
||||
return connector.triggerActivity(...arguments);
|
||||
}
|
||||
|
||||
function viewSourceInDebugger() {
|
||||
return connector.viewSourceInDebugger(...arguments);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
onConnect,
|
||||
onChromeConnect,
|
||||
onFirefoxConnect,
|
||||
onDisconnect,
|
||||
getLongString,
|
||||
getNetworkRequest,
|
||||
getTabTarget,
|
||||
inspectRequest,
|
||||
sendHTTPRequest,
|
||||
setPreferences,
|
||||
triggerActivity,
|
||||
viewSourceInDebugger,
|
||||
};
|
||||
module.exports.Connector = Connector;
|
||||
|
|
|
@ -25,6 +25,7 @@ const actionTypes = {
|
|||
SET_REQUEST_FILTER_TEXT: "SET_REQUEST_FILTER_TEXT",
|
||||
SORT_BY: "SORT_BY",
|
||||
TOGGLE_COLUMN: "TOGGLE_COLUMN",
|
||||
TOGGLE_RECORDING: "TOGGLE_RECORDING",
|
||||
TOGGLE_REQUEST_FILTER_TYPE: "TOGGLE_REQUEST_FILTER_TYPE",
|
||||
UPDATE_REQUEST: "UPDATE_REQUEST",
|
||||
WATERFALL_RESIZE: "WATERFALL_RESIZE",
|
||||
|
@ -53,6 +54,7 @@ const EVENTS = {
|
|||
// See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for
|
||||
// more information about what each packet is supposed to deliver.
|
||||
NETWORK_EVENT: "NetMonitor:NetworkEvent",
|
||||
NETWORK_EVENT_UPDATED: "NetMonitor:NetworkEventUpdated",
|
||||
TIMELINE_EVENT: "NetMonitor:TimelineEvent",
|
||||
|
||||
// When a network event is added to the view
|
||||
|
|
|
@ -15,10 +15,11 @@ add_task(function* () {
|
|||
|
||||
info("Starting test... ");
|
||||
|
||||
let { store, windowRequire } = monitor.panelWin;
|
||||
let { connector, store, windowRequire } = monitor.panelWin;
|
||||
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
|
||||
let RequestListContextMenu = windowRequire(
|
||||
"devtools/client/netmonitor/src/request-list-context-menu");
|
||||
let { getLongString, getTabTarget } = connector;
|
||||
|
||||
store.dispatch(Actions.batchEnable(false));
|
||||
|
||||
|
@ -26,7 +27,8 @@ add_task(function* () {
|
|||
tab.linkedBrowser.reload();
|
||||
yield wait;
|
||||
|
||||
let contextMenu = new RequestListContextMenu({});
|
||||
let contextMenu = new RequestListContextMenu({ getTabTarget, getLongString });
|
||||
|
||||
yield contextMenu.copyAllAsHar();
|
||||
|
||||
let jsonString = SpecialPowers.getClipboardData("text/unicode");
|
||||
|
|
|
@ -12,10 +12,11 @@ add_task(function* () {
|
|||
|
||||
info("Starting test... ");
|
||||
|
||||
let { store, windowRequire } = monitor.panelWin;
|
||||
let { connector, store, windowRequire } = monitor.panelWin;
|
||||
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
|
||||
let RequestListContextMenu = windowRequire(
|
||||
"devtools/client/netmonitor/src/request-list-context-menu");
|
||||
let { getLongString, getTabTarget } = connector;
|
||||
|
||||
store.dispatch(Actions.batchEnable(false));
|
||||
|
||||
|
@ -27,7 +28,7 @@ add_task(function* () {
|
|||
yield wait;
|
||||
|
||||
// Copy HAR into the clipboard (asynchronous).
|
||||
let contextMenu = new RequestListContextMenu({});
|
||||
let contextMenu = new RequestListContextMenu({ getTabTarget, getLongString });
|
||||
let jsonString = yield contextMenu.copyAllAsHar();
|
||||
let har = JSON.parse(jsonString);
|
||||
|
||||
|
|
|
@ -12,10 +12,11 @@ add_task(function* () {
|
|||
|
||||
info("Starting test... ");
|
||||
|
||||
let { store, windowRequire } = monitor.panelWin;
|
||||
let { connector, store, windowRequire } = monitor.panelWin;
|
||||
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
|
||||
let RequestListContextMenu = windowRequire(
|
||||
"devtools/client/netmonitor/src/request-list-context-menu");
|
||||
let { getLongString, getTabTarget } = connector;
|
||||
|
||||
store.dispatch(Actions.batchEnable(false));
|
||||
|
||||
|
@ -27,7 +28,7 @@ add_task(function* () {
|
|||
yield wait;
|
||||
|
||||
// Copy HAR into the clipboard (asynchronous).
|
||||
let contextMenu = new RequestListContextMenu({});
|
||||
let contextMenu = new RequestListContextMenu({ getTabTarget, getLongString });
|
||||
let jsonString = yield contextMenu.copyAllAsHar();
|
||||
let har = JSON.parse(jsonString);
|
||||
|
||||
|
|
|
@ -16,12 +16,11 @@ function* throttleUploadTest(actuallyThrottle) {
|
|||
|
||||
info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
|
||||
|
||||
let { store, windowRequire } = monitor.panelWin;
|
||||
let { connector, store, windowRequire } = monitor.panelWin;
|
||||
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
|
||||
let { setPreferences } =
|
||||
windowRequire("devtools/client/netmonitor/src/connector/index");
|
||||
let RequestListContextMenu = windowRequire(
|
||||
"devtools/client/netmonitor/src/request-list-context-menu");
|
||||
let { getLongString, getTabTarget, setPreferences } = connector;
|
||||
|
||||
store.dispatch(Actions.batchEnable(false));
|
||||
|
||||
|
@ -54,7 +53,7 @@ function* throttleUploadTest(actuallyThrottle) {
|
|||
yield wait;
|
||||
|
||||
// Copy HAR into the clipboard (asynchronous).
|
||||
let contextMenu = new RequestListContextMenu({});
|
||||
let contextMenu = new RequestListContextMenu({ getTabTarget, getLongString });
|
||||
let jsonString = yield contextMenu.copyAllAsHar();
|
||||
let har = JSON.parse(jsonString);
|
||||
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
DevToolsModules(
|
||||
'batching.js',
|
||||
'prefs.js',
|
||||
'recording.js',
|
||||
'thunk.js',
|
||||
)
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
TOGGLE_RECORDING,
|
||||
} = require("../constants");
|
||||
|
||||
const {
|
||||
getRecordingState,
|
||||
} = require("../selectors/index");
|
||||
|
||||
/**
|
||||
* Start/stop HTTP traffic recording.
|
||||
*
|
||||
* The UI state of the toolbar toggle button is stored in UI
|
||||
* reducer and the backend connection is managed here in the
|
||||
* middleware.
|
||||
*/
|
||||
function recordingMiddleware(connector) {
|
||||
return store => next => action => {
|
||||
const res = next(action);
|
||||
|
||||
// Pause/resume HTTP monitoring according to
|
||||
// the user action.
|
||||
if (action.type === TOGGLE_RECORDING) {
|
||||
let recording = getRecordingState(store.getState());
|
||||
if (recording) {
|
||||
connector.resume();
|
||||
} else {
|
||||
connector.pause();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = recordingMiddleware;
|
|
@ -14,6 +14,7 @@ const {
|
|||
REMOVE_SELECTED_CUSTOM_REQUEST,
|
||||
SELECT_REQUEST,
|
||||
SEND_CUSTOM_REQUEST,
|
||||
TOGGLE_RECORDING,
|
||||
UPDATE_REQUEST,
|
||||
UPDATE_PROPS,
|
||||
} = require("../constants");
|
||||
|
@ -68,6 +69,8 @@ const Requests = I.Record({
|
|||
// Auxiliary fields to hold requests stats
|
||||
firstStartedMillis: +Infinity,
|
||||
lastEndedMillis: -Infinity,
|
||||
// Recording state
|
||||
recording: true,
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -120,6 +123,64 @@ function requestsReducer(state = new Requests(), action) {
|
|||
}
|
||||
});
|
||||
}
|
||||
case CLEAR_REQUESTS: {
|
||||
return new Requests({
|
||||
recording: state.recording
|
||||
});
|
||||
}
|
||||
case CLONE_SELECTED_REQUEST: {
|
||||
let { requests, selectedId } = state;
|
||||
|
||||
if (!selectedId) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let clonedRequest = requests.get(selectedId);
|
||||
if (!clonedRequest) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let newRequest = new Request({
|
||||
id: clonedRequest.id + "-clone",
|
||||
method: clonedRequest.method,
|
||||
url: clonedRequest.url,
|
||||
urlDetails: clonedRequest.urlDetails,
|
||||
requestHeaders: clonedRequest.requestHeaders,
|
||||
requestPostData: clonedRequest.requestPostData,
|
||||
isCustom: true
|
||||
});
|
||||
|
||||
return state.withMutations(st => {
|
||||
st.requests = requests.set(newRequest.id, newRequest);
|
||||
st.selectedId = newRequest.id;
|
||||
});
|
||||
}
|
||||
case OPEN_NETWORK_DETAILS: {
|
||||
if (!action.open) {
|
||||
return state.set("selectedId", null);
|
||||
}
|
||||
|
||||
if (!state.selectedId && !state.requests.isEmpty()) {
|
||||
return state.set("selectedId", state.requests.first().id);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
case REMOVE_SELECTED_CUSTOM_REQUEST: {
|
||||
return closeCustomRequest(state);
|
||||
}
|
||||
case SELECT_REQUEST: {
|
||||
return state.set("selectedId", action.id);
|
||||
}
|
||||
case SEND_CUSTOM_REQUEST: {
|
||||
// When a new request with a given id is added in future, select it immediately.
|
||||
// where we know in advance the ID of the request, at a time when it
|
||||
// wasn't sent yet.
|
||||
return closeCustomRequest(state.set("preselectedId", action.id));
|
||||
}
|
||||
case TOGGLE_RECORDING: {
|
||||
return state.set("recording", !state.recording);
|
||||
}
|
||||
case UPDATE_REQUEST: {
|
||||
let { requests, lastEndedMillis } = state;
|
||||
|
||||
|
@ -160,59 +221,6 @@ function requestsReducer(state = new Requests(), action) {
|
|||
st.lastEndedMillis = lastEndedMillis;
|
||||
});
|
||||
}
|
||||
case CLEAR_REQUESTS: {
|
||||
return new Requests();
|
||||
}
|
||||
case SELECT_REQUEST: {
|
||||
return state.set("selectedId", action.id);
|
||||
}
|
||||
case CLONE_SELECTED_REQUEST: {
|
||||
let { requests, selectedId } = state;
|
||||
|
||||
if (!selectedId) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let clonedRequest = requests.get(selectedId);
|
||||
if (!clonedRequest) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let newRequest = new Request({
|
||||
id: clonedRequest.id + "-clone",
|
||||
method: clonedRequest.method,
|
||||
url: clonedRequest.url,
|
||||
urlDetails: clonedRequest.urlDetails,
|
||||
requestHeaders: clonedRequest.requestHeaders,
|
||||
requestPostData: clonedRequest.requestPostData,
|
||||
isCustom: true
|
||||
});
|
||||
|
||||
return state.withMutations(st => {
|
||||
st.requests = requests.set(newRequest.id, newRequest);
|
||||
st.selectedId = newRequest.id;
|
||||
});
|
||||
}
|
||||
case REMOVE_SELECTED_CUSTOM_REQUEST: {
|
||||
return closeCustomRequest(state);
|
||||
}
|
||||
case SEND_CUSTOM_REQUEST: {
|
||||
// When a new request with a given id is added in future, select it immediately.
|
||||
// where we know in advance the ID of the request, at a time when it
|
||||
// wasn't sent yet.
|
||||
return closeCustomRequest(state.set("preselectedId", action.id));
|
||||
}
|
||||
case OPEN_NETWORK_DETAILS: {
|
||||
if (!action.open) {
|
||||
return state.set("selectedId", null);
|
||||
}
|
||||
|
||||
if (!state.selectedId && !state.requests.isEmpty()) {
|
||||
return state.set("selectedId", state.requests.first().id);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
|
|
|
@ -10,10 +10,6 @@ const { gDevTools } = require("devtools/client/framework/devtools");
|
|||
const { saveAs } = require("devtools/client/shared/file-saver");
|
||||
const { copyString } = require("devtools/shared/platform/clipboard");
|
||||
const { HarExporter } = require("./har/har-exporter");
|
||||
const {
|
||||
getLongString,
|
||||
getTabTarget,
|
||||
} = require("./connector/index");
|
||||
const {
|
||||
getSelectedRequest,
|
||||
getSortedRequests,
|
||||
|
@ -28,9 +24,13 @@ const {
|
|||
|
||||
function RequestListContextMenu({
|
||||
cloneSelectedRequest,
|
||||
getLongString,
|
||||
getTabTarget,
|
||||
openStatistics,
|
||||
}) {
|
||||
this.cloneSelectedRequest = cloneSelectedRequest;
|
||||
this.getLongString = getLongString;
|
||||
this.getTabTarget = getTabTarget;
|
||||
this.openStatistics = openStatistics;
|
||||
}
|
||||
|
||||
|
@ -239,7 +239,7 @@ RequestListContextMenu.prototype = {
|
|||
* Opens selected item in the debugger
|
||||
*/
|
||||
openInDebugger() {
|
||||
let toolbox = gDevTools.getToolbox(getTabTarget());
|
||||
let toolbox = gDevTools.getToolbox(this.getTabTarget());
|
||||
toolbox.viewSourceInDebugger(this.selectedRequest.url, 0);
|
||||
},
|
||||
|
||||
|
@ -247,7 +247,7 @@ RequestListContextMenu.prototype = {
|
|||
* Opens selected item in the style editor
|
||||
*/
|
||||
openInStyleEditor() {
|
||||
let toolbox = gDevTools.getToolbox(getTabTarget());
|
||||
let toolbox = gDevTools.getToolbox(this.getTabTarget());
|
||||
toolbox.viewSourceInStyleEditor(this.selectedRequest.url, 0);
|
||||
},
|
||||
|
||||
|
@ -387,11 +387,11 @@ RequestListContextMenu.prototype = {
|
|||
},
|
||||
|
||||
getDefaultHarOptions() {
|
||||
let form = getTabTarget().form;
|
||||
let form = this.getTabTarget().form;
|
||||
let title = form.title || form.url;
|
||||
|
||||
return {
|
||||
getString: getLongString,
|
||||
getString: this.getLongString,
|
||||
items: this.sortedRequests,
|
||||
title: title
|
||||
};
|
||||
|
|
|
@ -8,19 +8,18 @@ const {
|
|||
setImageTooltip,
|
||||
getImageDimensions,
|
||||
} = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
|
||||
const { getLongString } = require("./connector/index");
|
||||
const { formDataURI } = require("./utils/request-utils");
|
||||
|
||||
const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400; // px
|
||||
|
||||
async function setTooltipImageContent(tooltip, itemEl, requestItem) {
|
||||
async function setTooltipImageContent(connector, tooltip, itemEl, requestItem) {
|
||||
let { mimeType, text, encoding } = requestItem.responseContent.content;
|
||||
|
||||
if (!mimeType || !mimeType.includes("image/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let string = await getLongString(text);
|
||||
let string = await connector.getLongString(text);
|
||||
let src = formDataURI(mimeType, encoding, string);
|
||||
let maxDim = REQUESTS_TOOLTIP_IMAGE_MAX_DIM;
|
||||
let { naturalWidth, naturalHeight } = await getImageDimensions(tooltip.doc, src);
|
||||
|
|
|
@ -127,10 +127,19 @@ function getDisplayedRequestById(state, id) {
|
|||
return getDisplayedRequests(state).find(r => r.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current recording boolean state (HTTP traffic is
|
||||
* monitored or not monitored)
|
||||
*/
|
||||
function getRecordingState(state) {
|
||||
return state.requests.recording;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDisplayedRequestById,
|
||||
getDisplayedRequests,
|
||||
getDisplayedRequestsSummary,
|
||||
getRecordingState,
|
||||
getRequestById,
|
||||
getSelectedRequest,
|
||||
getSortedRequests,
|
||||
|
|
|
@ -6,9 +6,14 @@
|
|||
|
||||
const Services = require("Services");
|
||||
const { applyMiddleware, createStore } = require("devtools/client/shared/vendor/redux");
|
||||
|
||||
// Middleware
|
||||
const batching = require("../middleware/batching");
|
||||
const prefs = require("../middleware/prefs");
|
||||
const thunk = require("../middleware/thunk");
|
||||
const recording = require("../middleware/recording");
|
||||
|
||||
// Reducers
|
||||
const rootReducer = require("../reducers/index");
|
||||
const { FilterTypes, Filters } = require("../reducers/filters");
|
||||
const { Requests } = require("../reducers/requests");
|
||||
|
@ -16,21 +21,40 @@ const { Sort } = require("../reducers/sort");
|
|||
const { TimingMarkers } = require("../reducers/timing-markers");
|
||||
const { UI, Columns } = require("../reducers/ui");
|
||||
|
||||
function configureStore() {
|
||||
const getPref = (pref) => {
|
||||
try {
|
||||
return JSON.parse(Services.prefs.getCharPref(pref));
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
/**
|
||||
* Configure state and middleware for the Network monitor tool.
|
||||
*/
|
||||
function configureStore(connector) {
|
||||
// Prepare initial state.
|
||||
const initialState = {
|
||||
filters: new Filters({
|
||||
requestFilterTypes: getFilterState()
|
||||
}),
|
||||
requests: new Requests(),
|
||||
sort: new Sort(),
|
||||
timingMarkers: new TimingMarkers(),
|
||||
ui: new UI({
|
||||
columns: getColumnState()
|
||||
}),
|
||||
};
|
||||
|
||||
let activeFilters = {};
|
||||
let filters = getPref("devtools.netmonitor.filters");
|
||||
filters.forEach((filter) => {
|
||||
activeFilters[filter] = true;
|
||||
});
|
||||
// Prepare middleware.
|
||||
let middleware = applyMiddleware(
|
||||
thunk,
|
||||
prefs,
|
||||
batching,
|
||||
recording(connector)
|
||||
);
|
||||
|
||||
return createStore(rootReducer, initialState, middleware);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Get column state from preferences.
|
||||
*/
|
||||
function getColumnState() {
|
||||
let columns = new Columns();
|
||||
let visibleColumns = getPref("devtools.netmonitor.visibleColumns");
|
||||
|
||||
|
@ -40,19 +64,27 @@ function configureStore() {
|
|||
});
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
filters: new Filters({
|
||||
requestFilterTypes: new FilterTypes(activeFilters)
|
||||
}),
|
||||
requests: new Requests(),
|
||||
sort: new Sort(),
|
||||
timingMarkers: new TimingMarkers(),
|
||||
ui: new UI({
|
||||
columns,
|
||||
}),
|
||||
};
|
||||
return columns;
|
||||
}
|
||||
|
||||
return createStore(rootReducer, initialState, applyMiddleware(thunk, prefs, batching));
|
||||
/**
|
||||
* Get filter state from preferences.
|
||||
*/
|
||||
function getFilterState() {
|
||||
let activeFilters = {};
|
||||
let filters = getPref("devtools.netmonitor.filters");
|
||||
filters.forEach((filter) => {
|
||||
activeFilters[filter] = true;
|
||||
});
|
||||
return new FilterTypes(activeFilters);
|
||||
}
|
||||
|
||||
function getPref(pref) {
|
||||
try {
|
||||
return JSON.parse(Services.prefs.getCharPref(pref));
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
exports.configureStore = configureStore;
|
||||
|
|
|
@ -25,6 +25,7 @@ support-files =
|
|||
html_maps-test-page.html
|
||||
html_navigate-test-page.html
|
||||
html_params-test-page.html
|
||||
html_pause-test-page.html
|
||||
html_post-data-test-page.html
|
||||
html_post-json-test-page.html
|
||||
html_post-raw-test-page.html
|
||||
|
@ -135,6 +136,7 @@ skip-if = (os == 'linux' && debug && bits == 32) # Bug 1303439
|
|||
[browser_net_open_request_in_tab.js]
|
||||
[browser_net_pane-collapse.js]
|
||||
[browser_net_pane-toggle.js]
|
||||
[browser_net_pause.js]
|
||||
[browser_net_params_sorted.js]
|
||||
[browser_net_persistent_logs.js]
|
||||
[browser_net_post-data-01.js]
|
||||
|
|
|
@ -13,12 +13,12 @@ add_task(function* () {
|
|||
let { tab, monitor } = yield initNetMonitor(CURL_UTILS_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { store, windowRequire } = monitor.panelWin;
|
||||
let { store, windowRequire, connector } = monitor.panelWin;
|
||||
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
|
||||
let {
|
||||
getSortedRequests,
|
||||
} = windowRequire("devtools/client/netmonitor/src/selectors/index");
|
||||
let { getLongString } = windowRequire("devtools/client/netmonitor/src/connector/index");
|
||||
let { getLongString } = connector;
|
||||
|
||||
store.dispatch(Actions.batchEnable(false));
|
||||
|
||||
|
|
|
@ -12,10 +12,9 @@ add_task(function* () {
|
|||
const SELECTOR = ".requests-list-icon[src]";
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, store, windowRequire } = monitor.panelWin;
|
||||
let { document, store, windowRequire, connector } = monitor.panelWin;
|
||||
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
|
||||
let { triggerActivity } =
|
||||
windowRequire("devtools/client/netmonitor/src/connector/index");
|
||||
let { triggerActivity } = connector;
|
||||
let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/src/constants");
|
||||
|
||||
store.dispatch(Actions.batchEnable(false));
|
||||
|
|
|
@ -14,10 +14,9 @@ add_task(function* test() {
|
|||
const SELECTOR = ".requests-list-icon[src]";
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, store, windowRequire } = monitor.panelWin;
|
||||
let { document, store, windowRequire, connector } = monitor.panelWin;
|
||||
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
|
||||
let { triggerActivity } =
|
||||
windowRequire("devtools/client/netmonitor/src/connector/index");
|
||||
let { triggerActivity } = connector;
|
||||
let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/src/constants");
|
||||
let toolboxDoc = monitor.panelWin.parent.document;
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the pause/resume button works.
|
||||
*/
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(PAUSE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, store, windowRequire, connector } = monitor.panelWin;
|
||||
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
|
||||
let pauseButton = document.querySelector(".requests-list-pause-button");
|
||||
|
||||
store.dispatch(Actions.batchEnable(false));
|
||||
|
||||
// Make sure we start in a sane state.
|
||||
assertRequestCount(store, 0);
|
||||
|
||||
// Load one request and assert it shows up in the list.
|
||||
yield performRequestAndWait(tab, monitor);
|
||||
assertRequestCount(store, 1);
|
||||
|
||||
let noRequest = true;
|
||||
monitor.panelWin.once(EVENTS.NETWORK_EVENT, () => {
|
||||
noRequest = false;
|
||||
});
|
||||
|
||||
monitor.panelWin.once(EVENTS.NETWORK_EVENT_UPDATED, () => {
|
||||
noRequest = false;
|
||||
});
|
||||
|
||||
// Click pause, load second request and make sure they don't show up.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, pauseButton);
|
||||
yield performPausedRequest(connector, tab, monitor);
|
||||
ok(noRequest, "There should be no activity when paused.");
|
||||
assertRequestCount(store, 1);
|
||||
|
||||
// Click pause again to resume monitoring. Load a third request
|
||||
// and make sure they will show up.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, pauseButton);
|
||||
yield performRequestAndWait(tab, monitor);
|
||||
assertRequestCount(store, 2);
|
||||
|
||||
return teardown(monitor);
|
||||
});
|
||||
|
||||
/**
|
||||
* Asserts the number of requests in the network monitor.
|
||||
*/
|
||||
function assertRequestCount(store, count) {
|
||||
is(store.getState().requests.requests.size, count,
|
||||
"There should be correct number of requests");
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute simple GET request and wait till it's done.
|
||||
*/
|
||||
function* performRequestAndWait(tab, monitor) {
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
|
||||
yield content.wrappedJSObject.performRequests(url);
|
||||
});
|
||||
yield wait;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute simple GET request
|
||||
*/
|
||||
function* performPausedRequest(connector, tab, monitor) {
|
||||
let wait = waitForWebConsoleNetworkEvent(connector);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
|
||||
yield content.wrappedJSObject.performRequests(url);
|
||||
});
|
||||
yield wait;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for events fired by the console client since the Firefox
|
||||
* connector (data provider) is paused.
|
||||
*/
|
||||
function waitForWebConsoleNetworkEvent(connector) {
|
||||
return new Promise(resolve => {
|
||||
connector.connector.webConsoleClient.once("networkEvent", (type, networkInfo) => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|