merge autoland to mozilla-central. r=merge a=merge

MozReview-Commit-ID: JRsSap6SwOZ
This commit is contained in:
Sebastian Hengst 2017-10-17 11:42:24 +02:00
Родитель 79aa12fe2c ee83cd683f
Коммит af89102d41
687 изменённых файлов: 5966 добавлений и 4935 удалений

Просмотреть файл

@ -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

Двоичные данные
browser/branding/aurora/bgstub.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
browser/branding/aurora/bgstub_2x.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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

Двоичные данные
browser/branding/nightly/bgstub.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
browser/branding/nightly/bgstub_2x.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 49 KiB

Двоичные данные
browser/branding/official/bgstub.bmp

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 809 KiB

Двоичные данные
browser/branding/official/bgstub.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 18 KiB

Двоичные данные
browser/branding/official/bgstub_2x.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 88 KiB

Двоичные данные
browser/branding/unofficial/bgstub.bmp

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 809 KiB

Двоичные данные
browser/branding/unofficial/bgstub.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 17 KiB

Двоичные данные
browser/branding/unofficial/bgstub_2x.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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 = () => {},
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}`);
}
}
function onDisconnect() {
connector && connector.disconnect();
disconnect() {
this.connector && this.connector.disconnect();
}
function onChromeConnect(connection, actions, getState) {
connector = require("./chrome-connector");
connector.connect(connection, actions, getState);
connectChrome(connection, actions, getState) {
this.connector = require("./chrome-connector");
this.connector.connect(connection, actions, getState);
}
function onFirefoxConnect(connection, actions, getState) {
connector = require("./firefox-connector");
connector.connect(connection, actions, getState);
connectFirefox(connection, actions, getState) {
this.connector = require("./firefox-connector");
this.connector.connect(connection, actions, getState);
}
function inspectRequest() {
return connector.inspectRequest(...arguments);
pause() {
this.connector.pause();
}
function getLongString() {
return connector.getLongString(...arguments);
resume() {
this.connector.resume();
}
function getNetworkRequest() {
return connector.getNetworkRequest(...arguments);
// Public API
getLongString() {
return this.connector.getLongString(...arguments);
}
function getTabTarget() {
return connector.getTabTarget();
getNetworkRequest() {
return this.connector.getNetworkRequest(...arguments);
}
function sendHTTPRequest() {
return connector.sendHTTPRequest(...arguments);
getTabTarget() {
return this.connector.getTabTarget();
}
function setPreferences() {
return connector.setPreferences(...arguments);
sendHTTPRequest() {
return this.connector.sendHTTPRequest(...arguments);
}
function triggerActivity() {
return connector.triggerActivity(...arguments);
setPreferences() {
return this.connector.setPreferences(...arguments);
}
function viewSourceInDebugger() {
return connector.viewSourceInDebugger(...arguments);
triggerActivity() {
return this.connector.triggerActivity(...arguments);
}
module.exports = {
onConnect,
onChromeConnect,
onFirefoxConnect,
onDisconnect,
getLongString,
getNetworkRequest,
getTabTarget,
inspectRequest,
sendHTTPRequest,
setPreferences,
triggerActivity,
viewSourceInDebugger,
};
viewSourceInDebugger() {
return this.connector.viewSourceInDebugger(...arguments);
}
}
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();
});
});
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше