MozReview-Commit-ID: GdyXEYZsVuX
This commit is contained in:
Kartikaya Gupta 2017-04-25 08:32:48 -04:00
Родитель 37ebb37efc 9155c12847
Коммит b21511b7c0
662 изменённых файлов: 29924 добавлений и 24396 удалений

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

@ -999,6 +999,13 @@ pref("security.sandbox.content.level", 1);
// allow stack tracing. This does not require a restart to take effect.
pref("security.sandbox.windows.log.stackTraceDepth", 0);
#endif
// This controls the strength of the Windows GPU process sandbox. Changes
// will require restart.
// For information on what the level number means, see
// SetSecurityLevelForGPUProcess() in
// security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
pref("security.sandbox.gpu.level", 1);
#endif
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)

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

@ -5103,7 +5103,8 @@ nsBrowserAccess.prototype = {
_openURIInNewTab(aURI, aReferrer, aReferrerPolicy, aIsPrivate,
aIsExternal, aForceNotRemote = false,
aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
aOpener = null, aTriggeringPrincipal = null) {
aOpener = null, aTriggeringPrincipal = null,
aNextTabParentId = 0) {
let win, needToFocusWin;
// try the current window. if we're in a popup, fall back on the most recent browser window
@ -5136,6 +5137,7 @@ nsBrowserAccess.prototype = {
inBackground: loadInBackground,
forceNotRemote: aForceNotRemote,
opener: aOpener,
nextTabParentId: aNextTabParentId,
});
let browser = win.gBrowser.getBrowserForTab(tab);
@ -5238,7 +5240,8 @@ nsBrowserAccess.prototype = {
return newWindow;
},
openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aFlags) {
openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aFlags,
aNextTabParentId) {
if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
dump("Error: openURIInFrame can only open in new tabs");
return null;
@ -5257,7 +5260,8 @@ nsBrowserAccess.prototype = {
aParams.isPrivate,
isExternal, false,
userContextId, null,
aParams.triggeringPrincipal);
aParams.triggeringPrincipal,
aNextTabParentId);
if (browser)
return browser.QueryInterface(Ci.nsIFrameLoaderOwner);

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

@ -165,6 +165,7 @@
hidden="true"
orient="vertical"
noautofocus="true"
noautohide="true"
consumeoutsideclicks="false"
level="parent"
tabspecific="true">
@ -1071,6 +1072,17 @@
<deck id="content-deck" flex="1">
<hbox flex="1" id="browser">
<vbox id="browser-border-start" hidden="true" layer="true"/>
<vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
<sidebarheader id="sidebar-header" align="center">
<label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/>
<image id="sidebar-throbber"/>
<toolbarbutton class="close-icon tabbable" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="SidebarUI.hide();"/>
</sidebarheader>
<browser id="sidebar" flex="1" autoscroll="false" disablehistory="true" disablefullscreen="true"
style="min-width: 14em; width: 18em; max-width: 36em;" tooltip="aHTMLTooltip"/>
</vbox>
<splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
<vbox id="appcontent" flex="1">
<notificationbox id="high-priority-global-notificationbox" notificationside="top"/>
<tabbrowser id="content"
@ -1081,16 +1093,6 @@
selectmenulist="ContentSelectDropdown"
datetimepicker="DateTimePickerPanel"/>
</vbox>
<splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
<vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
<sidebarheader id="sidebar-header" align="center">
<label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/>
<image id="sidebar-throbber"/>
<toolbarbutton class="close-icon tabbable" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="SidebarUI.hide();"/>
</sidebarheader>
<browser id="sidebar" flex="1" autoscroll="false" disablehistory="true" disablefullscreen="true"
style="min-width: 14em; width: 18em; max-width: 36em;" tooltip="aHTMLTooltip"/>
</vbox>
<vbox id="browser-border-end" hidden="true" layer="true"/>
</hbox>
#include ../../components/customizableui/content/customizeMode.inc.xul

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

@ -1488,6 +1488,7 @@
var aOriginPrincipal;
var aOpener;
var aCreateLazyBrowser;
var aNextTabParentId;
if (arguments.length == 2 &&
typeof arguments[1] == "object" &&
!(arguments[1] instanceof Ci.nsIURI)) {
@ -1512,6 +1513,7 @@
aOpener = params.opener;
aIsPrerendered = params.isPrerendered;
aCreateLazyBrowser = params.createLazyBrowser;
aNextTabParentId = params.nextTabParentId;
}
var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
@ -1538,7 +1540,8 @@
originPrincipal: aOriginPrincipal,
sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
opener: aOpener,
isPrerendered: aIsPrerendered });
isPrerendered: aIsPrerendered,
nextTabParentId: aNextTabParentId });
if (!bgLoad)
this.selectedTab = tab;
@ -1974,6 +1977,11 @@
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
if (aParams.nextTabParentId) {
// Gecko is going to read this attribute and use it.
b.setAttribute("nextTabParentId", aParams.nextTabParentId.toString());
}
if (aParams.sameProcessAsFrameLoader) {
b.sameProcessAsFrameLoader = aParams.sameProcessAsFrameLoader;
}
@ -2218,6 +2226,7 @@
var aOpener;
var aCreateLazyBrowser;
var aSkipBackgroundNotify;
var aNextTabParentId;
if (arguments.length == 2 &&
typeof arguments[1] == "object" &&
!(arguments[1] instanceof Ci.nsIURI)) {
@ -2245,6 +2254,7 @@
aIsPrerendered = params.isPrerendered;
aCreateLazyBrowser = params.createLazyBrowser;
aSkipBackgroundNotify = params.skipBackgroundNotify;
aNextTabParentId = params.nextTabParentId;
}
// if we're adding tabs, we're past interrupt mode, ditch the owner
@ -2353,7 +2363,8 @@
userContextId: aUserContextId,
sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
opener: aOpener,
isPrerendered: aIsPrerendered });
isPrerendered: aIsPrerendered,
nextTabParentId: aNextTabParentId });
}
t.linkedBrowser = b;

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

@ -145,27 +145,25 @@ add_task(function* () {
let sidebar = document.getElementById("sidebar");
let loadPromise = BrowserTestUtils.waitForEvent(sidebar, "load", true);
let focusPromise = BrowserTestUtils.waitForEvent(sidebar, "focus", true);
SidebarUI.toggle("viewBookmarksSidebar");
yield loadPromise;
yield focusPromise;
gURLBar.focus();
yield* expectFocusOnF6(false, "html1", "html1",
true, "focus with sidebar open content");
yield* expectFocusOnF6(false, "bookmarksPanel",
sidebar.contentDocument.getElementById("search-box").inputField,
false, "focus with sidebar open sidebar");
yield* expectFocusOnF6(false, "html1", "html1",
true, "focus with sidebar open content");
yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
false, "focus with sidebar urlbar");
// Now go backwards
yield* expectFocusOnF6(true, "html1", "html1",
true, "back focus with sidebar open content");
yield* expectFocusOnF6(true, "bookmarksPanel",
sidebar.contentDocument.getElementById("search-box").inputField,
false, "back focus with sidebar open sidebar");
yield* expectFocusOnF6(true, "html1", "html1",
true, "back focus with sidebar open content");
yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
false, "back focus with sidebar urlbar");

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

@ -201,8 +201,8 @@ var tests = [
});
notification.remove();
goNext();
});
goNext();
},
},
];

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

@ -118,6 +118,26 @@ add_task(function* () {
EventUtils.synthesizeKey(" ", {});
yield expectEvent("on-input-started-fired");
// Test canceling the input before any changed events fire.
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
yield expectEvent("on-input-cancelled-fired");
EventUtils.synthesizeKey(" ", {});
yield expectEvent("on-input-started-fired");
// Test submitting the input before any changed events fire.
EventUtils.synthesizeKey("VK_RETURN", {});
yield expectEvent("on-input-entered-fired");
gURLBar.focus();
// Start an input session by typing in <keyword><space>.
for (let letter of keyword) {
EventUtils.synthesizeKey(letter, {});
}
EventUtils.synthesizeKey(" ", {});
yield expectEvent("on-input-started-fired");
// We should expect input changed events now that the keyword is active.
EventUtils.synthesizeKey("b", {});
yield expectEvent("on-input-changed-fired", {text: "b"});

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

@ -780,24 +780,10 @@ BrowserGlue.prototype = {
},
_firstWindowTelemetry(aWindow) {
let SCALING_PROBE_NAME = "";
switch (AppConstants.platform) {
case "win":
SCALING_PROBE_NAME = "DISPLAY_SCALING_MSWIN";
break;
case "macosx":
SCALING_PROBE_NAME = "DISPLAY_SCALING_OSX";
break;
case "linux":
SCALING_PROBE_NAME = "DISPLAY_SCALING_LINUX";
break;
}
if (SCALING_PROBE_NAME) {
let scaling = aWindow.devicePixelRatio * 100;
try {
Services.telemetry.getHistogramById(SCALING_PROBE_NAME).add(scaling);
} catch (ex) {}
}
let scaling = aWindow.devicePixelRatio * 100;
try {
Services.telemetry.getHistogramById("DISPLAY_SCALING").add(scaling);
} catch (ex) {}
},
// the first browser window has finished initializing

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

@ -65,6 +65,7 @@ support-files =
[browser_favicon_userContextId.js]
[browser_firstPartyIsolation.js]
[browser_firstPartyIsolation_aboutPages.js]
[browser_firstPartyIsolation_blobURI.js]
[browser_firstPartyIsolation_js_uri.js]
[browser_localStorageIsolation.js]
[browser_blobURLIsolation.js]

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

@ -5,6 +5,7 @@ add_task(function* setup() {
Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
registerCleanupFunction(function() {
Services.prefs.clearUserPref("privacy.firstparty.isolate");
Services.cookies.removeAll();
});
});

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

@ -0,0 +1,76 @@
add_task(function* setup() {
Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
registerCleanupFunction(function() {
Services.prefs.clearUserPref("privacy.firstparty.isolate");
Services.cookies.removeAll();
});
});
/**
* First we generate a Blob URI by using URL.createObjectURL(new Blob(..));
* then we navigate to this Blob URI, hence to make the top-level document URI
* is Blob URI.
* Later we create an iframe on this Blob: document, and we test that the iframe
* has correct firstPartyDomain.
*/
add_task(function* test_blob_uri_inherit_oa_from_content() {
const BASE_URI = "http://mochi.test:8888/browser/browser/components/" +
"originattributes/test/browser/dummy.html";
const BASE_DOMAIN = "mochi.test";
// First we load a normal web page.
let win = yield BrowserTestUtils.openNewBrowserWindow({ remote: true });
let browser = win.gBrowser.selectedBrowser;
browser.loadURI(BASE_URI);
yield BrowserTestUtils.browserLoaded(browser);
// Then navigate to the blob: URI.
yield ContentTask.spawn(browser, { firstPartyDomain: BASE_DOMAIN }, function* (attrs) {
info("origin " + content.document.nodePrincipal.origin);
Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
attrs.firstPartyDomain, "The document should have firstPartyDomain");
// Now we use createObjectURL to generate a blob URI and navigate to it.
let url = content.window.URL.createObjectURL(new content.window.Blob([
`<script src="http://mochi.test:8888/browser/browser/components/originattributes/test/browser/test.js"></script>`],
{"type": "text/html"}));
content.document.location = url;
});
// Wait for the Blob: URI to be loaded.
yield BrowserTestUtils.browserLoaded(browser, false, function(url) {
info("BrowserTestUtils.browserLoaded url=" + url);
return url.startsWith("blob:http://mochi.test:8888/");
});
// We verify the blob document has correct origin attributes.
// Then we inject an iframe to it.
yield ContentTask.spawn(browser, { firstPartyDomain: BASE_DOMAIN }, function* (attrs) {
Assert.ok(content.document.documentURI.startsWith("blob:http://mochi.test:8888/"),
"the document URI should be a blob URI.");
info("origin " + content.document.nodePrincipal.origin);
Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
attrs.firstPartyDomain, "The document should have firstPartyDomain");
let iframe = content.document.createElement("iframe");
iframe.src = "http://example.com";
iframe.id = "iframe1";
content.document.body.appendChild(iframe);
});
// Wait for the iframe to be loaded.
// yield BrowserTestUtils.browserLoaded(browser, true, function(url) {
// info("BrowserTestUtils.browserLoaded iframe url=" + url);
// return url == "http://example.com/";
// });
// Finally we verify the iframe has correct origin attributes.
yield ContentTask.spawn(browser, { firstPartyDomain: BASE_DOMAIN }, function* (attrs) {
let iframe = content.document.getElementById("iframe1");
Assert.equal(iframe.contentDocument.nodePrincipal.originAttributes.firstPartyDomain,
attrs.firstPartyDomain, "iframe should inherit firstPartyDomain from blob: URI");
});
win.close();
});

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

@ -750,6 +750,9 @@
#ifdef MOZ_DATA_REPORTING
#ifdef MOZ_CRASHREPORTER
<groupbox id="crashReporterGroup" data-category="panePrivacy" data-subcategory="reports" hidden="true">
#ifndef MOZ_TELEMETRY_REPORTING
<caption><label>&reports.label;</label></caption>
#endif
<hbox align="center">
<checkbox id="automaticallySubmitCrashesBox"
preference="browser.crashReports.unsubmittedCheck.autoSubmit"

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

@ -30,7 +30,6 @@ XPIDL_MODULE = 'shellservice'
if CONFIG['OS_ARCH'] == 'WINNT':
SOURCES += [
'../../../other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp',
'nsWindowsShellService.cpp',
]
LOCAL_INCLUDES += [

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

@ -6,7 +6,7 @@
.profile-item-box > .profile-item-col > .profile-label {
font-size: .9em;
font-size: .84em;
}
.profile-item-box > .profile-item-col > .profile-comment {

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

@ -6,7 +6,7 @@
.profile-item-box > .profile-item-col > .profile-label {
font-size: 1.18em;
font-size: 1.09em;
}
.profile-item-box > .profile-item-col > .profile-comment {

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

@ -10,13 +10,27 @@ xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .profile-it
background-color: #F2F2F2;
}
.profile-item-box {
--item-padding-vertical: 6px;
--item-padding-horizontal: 10px;
--col-spacer: 7px;
--item-width: calc(50% - (var(--col-spacer) / 2));
}
.profile-item-box[size="small"] {
--item-padding-vertical: 7px;
--col-spacer: 0px;
--row-spacer: 3px;
--item-width: 100%;
}
.profile-item-box {
box-sizing: border-box;
margin: 0;
border-bottom: 1px solid rgba(38,38,38,.15);
padding-top: 6px;
padding-bottom: 6px;
padding-inline-start: 16px;
padding-inline-end: 10px;
padding: var(--item-padding-vertical) 0;
padding-inline-start: var(--item-padding-horizontal);
padding-inline-end: var(--item-padding-horizontal);
display: flex;
flex-direction: row;
flex-wrap: wrap;
@ -33,34 +47,24 @@ xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .profile-it
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 50%;
width: var(--item-width);
}
.profile-item-box > .profile-label-col {
padding-inline-end: 10px;
text-align: start;
}
.profile-item-box > .profile-comment-col {
margin-inline-start: var(--col-spacer);
text-align: end;
color: GrayText;
}
.profile-item-box[size="small"] {
padding-top: 10px;
padding-bottom: 10px;
flex-direction: column;
}
.profile-item-box[size="small"] > .profile-item-col {
width: 100%;
}
.profile-item-box[size="small"] > .profile-label-col {
padding-inline-end: 0;
}
.profile-item-box[size="small"] > .profile-comment-col {
margin-top: 7px;
margin-top: var(--row-spacer);
text-align: start;
}

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

@ -6,10 +6,6 @@
@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
.profile-item-box > .profile-item-col > .profile-label {
font-size: 1.08em;
}
.profile-item-box > .profile-item-col > .profile-comment {
font-size: .83em;
}

31
browser/extensions/pocket/bootstrap.js поставляемый
Просмотреть файл

@ -16,7 +16,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
"resource://gre/modules/ReaderMode.jsm");
@ -477,21 +477,20 @@ function prefObserver(aSubject, aTopic, aData) {
}
function startup(data, reason) {
AddonManager.getAddonByID("isreaditlater@ideashower.com", addon => {
if (addon && addon.isActive)
return;
setDefaultPrefs();
// migrate enabled pref
if (Services.prefs.prefHasUserValue("browser.pocket.enabled")) {
Services.prefs.setBoolPref("extensions.pocket.enabled", Services.prefs.getBoolPref("browser.pocket.enabled"));
Services.prefs.clearUserPref("browser.pocket.enabled");
}
// watch pref change and enable/disable if necessary
Services.prefs.addObserver("extensions.pocket.enabled", prefObserver);
if (!Services.prefs.getBoolPref("extensions.pocket.enabled"))
return;
PocketOverlay.startup(reason);
});
if (AddonManagerPrivate.addonIsActive("isreaditlater@ideashower.com"))
return;
setDefaultPrefs();
// migrate enabled pref
if (Services.prefs.prefHasUserValue("browser.pocket.enabled")) {
Services.prefs.setBoolPref("extensions.pocket.enabled", Services.prefs.getBoolPref("browser.pocket.enabled"));
Services.prefs.clearUserPref("browser.pocket.enabled");
}
// watch pref change and enable/disable if necessary
Services.prefs.addObserver("extensions.pocket.enabled", prefObserver);
if (!Services.prefs.getBoolPref("extensions.pocket.enabled"))
return;
PocketOverlay.startup(reason);
}
function shutdown(data, reason) {

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

@ -68,6 +68,7 @@ DEFINES += -DMOZ_CHILD_PROCESS_NAME=$(MOZ_CHILD_PROCESS_NAME)
# Set MSVC dlls version to package, if any.
ifdef MOZ_NO_DEBUG_RTL
ifdef WIN32_REDIST_DIR
ifndef MOZ_ARTIFACT_BUILDS
DEFINES += -DMOZ_PACKAGE_MSVC_DLLS=1
DEFINES += -DMSVC_C_RUNTIME_DLL=$(MSVC_C_RUNTIME_DLL)
DEFINES += -DMSVC_CXX_RUNTIME_DLL=$(MSVC_CXX_RUNTIME_DLL)
@ -76,6 +77,7 @@ ifdef WIN_UCRT_REDIST_DIR
DEFINES += -DMOZ_PACKAGE_WIN_UCRT_DLLS=1
endif
endif
endif
ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET)))
DEFINES += -DMOZ_SHARED_MOZGLUE=1

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

@ -7,7 +7,7 @@
<Description>楽天市場 商品検索</Description>
<InputEncoding>EUC-JP</InputEncoding>
<Image width="16" height="16">data:image/x-icon;base64,R0lGODlhEAAQALMOAOefn9psbMswMMMQEPjj4/PPz++/v+OPj99/f9+AgMcgIM9AQOuvr////wAAAL8AACH5BAEAAA4ALAAAAAAQABAAAARf0EnJ0FqIze2YemCoaBsQniEwMeIloOQHJk1NvKDSnTRQNIZThddYPAiNk4UYCDQIpwux1ghEEUTGc6BkhWiLA1DokD3Ag5/1odvlFlzFAkdymFAn1caDH3FWFhh1EhEAOw==</Image>
<Url type="text/html" method="GET" template="http://pt.afl.rakuten.co.jp/c/013ca98b.cd7c5f0c/" resultdomain="rakuten.co.jp">
<Url type="text/html" method="GET" template="https://pt.afl.rakuten.co.jp/c/013ca98b.cd7c5f0c/" resultdomain="rakuten.co.jp">
<Param name="sitem" value="{searchTerms}"/>
<Param name="sv" value="2"/>
<Param name="p" value="0"/>

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

@ -398,7 +398,9 @@ this.ExtensionsUI = {
label: strings.acceptText,
accessKey: strings.acceptKey,
callback: () => {
this.histogram.add(histkey + "Accepted");
if (histkey) {
this.histogram.add(histkey + "Accepted");
}
resolve(true);
},
};
@ -407,7 +409,9 @@ this.ExtensionsUI = {
label: strings.cancelText,
accessKey: strings.cancelKey,
callback: () => {
this.histogram.add(histkey + "Rejected");
if (histkey) {
this.histogram.add(histkey + "Rejected");
}
resolve(false);
},
},

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

@ -10,6 +10,7 @@
#include "mozilla/dom/quota/QuotaManager.h"
#include "nsIEffectiveTLDService.h"
#include "nsIURI.h"
#include "nsIURIWithPrincipal.h"
namespace mozilla {
@ -52,16 +53,29 @@ OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
nsAutoCString baseDomain;
nsresult rv = tldService->GetBaseDomain(aURI, 0, baseDomain);
if (NS_FAILED(rv)) {
nsAutoCString scheme;
rv = aURI->GetScheme(scheme);
NS_ENSURE_SUCCESS_VOID(rv);
if (scheme.EqualsLiteral("about")) {
baseDomain.AssignLiteral(ABOUT_URI_FIRST_PARTY_DOMAIN);
}
if (NS_SUCCEEDED(rv)) {
mFirstPartyDomain = NS_ConvertUTF8toUTF16(baseDomain);
return;
}
mFirstPartyDomain = NS_ConvertUTF8toUTF16(baseDomain);
nsAutoCString scheme;
rv = aURI->GetScheme(scheme);
NS_ENSURE_SUCCESS_VOID(rv);
if (scheme.EqualsLiteral("about")) {
mFirstPartyDomain.AssignLiteral(ABOUT_URI_FIRST_PARTY_DOMAIN);
} else if (scheme.EqualsLiteral("blob")) {
nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(aURI);
if (uriPrinc) {
nsCOMPtr<nsIPrincipal> principal;
rv = uriPrinc->GetPrincipal(getter_AddRefs(principal));
NS_ENSURE_SUCCESS_VOID(rv);
MOZ_ASSERT(principal, "blob URI but no principal.");
if (principal) {
mFirstPartyDomain = principal->OriginAttributesRef().mFirstPartyDomain;
}
}
}
}
void

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

@ -46,7 +46,7 @@ EXEC = exec
# ELOG prints out failed command when building silently (gmake -s). Pymake
# prints out failed commands anyway, so ELOG just makes things worse by
# forcing shell invocations.
ifneq (,$(findstring s, $(filter-out --%, $(MAKEFLAGS))))
ifneq (,$(findstring -s, $(filter-out --%, $(MAKEFLAGS))))
ELOG := $(EXEC) sh $(BUILD_TOOLS)/print-failed-commands.sh
else
ELOG :=
@ -953,9 +953,12 @@ ifdef MOZ_MSVCBITS
# a 32-bit MozillaBuild shell on a 64-bit machine will try to use
# the 32-bit compiler/linker for everything, while cargo/rustc wants
# to use the 64-bit linker for build.rs scripts. This conflict results
# in a build failure (see bug 1350001). Clearing out *just* the changes
# from vcvars.bat is hard, so we just clear out the whole environment.
environment_cleaner = -i
# in a build failure (see bug 1350001). So we clear out the environment
# variables that are actually relevant to 32- vs 64-bit builds.
environment_cleaner = PATH='' LIB='' LIBPATH=''
# The servo build needs to know where python is, and we're removing the PATH
# so we tell it explicitly via the PYTHON env var.
environment_cleaner += PYTHON='$(shell which $(PYTHON))'
else
environment_cleaner =
endif

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

@ -249,6 +249,25 @@ button {
font-weight: 600;
}
.addon-target-info {
display: grid;
font-size: 14px;
grid-template-columns: 128px 1fr;
}
.addon-target-info-label {
padding-inline-end: 8px;
padding-bottom: 8px;
}
.addon-target-info-label:last-of-type {
padding-bottom: 16px;
}
.addon-target-info-content {
margin: 0;
}
.addon-target-button {
background: none;
border: none;
@ -289,3 +308,18 @@ button {
* lines up with the icon. */
margin-inline-start: -4px;
}
/* We want the ellipsis on the left-hand-side, so make the parent RTL
* with an ellipsis and the child can be LTR. */
.file-path {
direction: rtl;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-path-inner {
direction: ltr;
unicode-bidi: plaintext;
}

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

@ -76,7 +76,8 @@ module.exports = createClass({
icon: addon.iconURL || ExtensionIcon,
addonID: addon.id,
addonActor: addon.actor,
temporarilyInstalled: addon.temporarilyInstalled
temporarilyInstalled: addon.temporarilyInstalled,
url: addon.url,
};
});

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

@ -20,6 +20,25 @@ loader.lazyRequireGetter(this, "DebuggerClient",
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
function filePathForTarget(target) {
// Only show file system paths, and only for temporarily installed add-ons.
if (!target.temporarilyInstalled || !target.url || !target.url.startsWith("file://")) {
return [];
}
let path = target.url.slice("file://".length);
return [
dom.dt(
{ className: "addon-target-info-label" },
Strings.GetStringFromName("location")),
// Wrap the file path in a span so we can do some RTL/LTR swapping to get
// the ellipsis on the left.
dom.dd(
{ className: "addon-target-info-content file-path" },
dom.span({ className: "file-path-inner", title: path }, path),
),
];
}
module.exports = createClass({
displayName: "AddonTarget",
@ -31,7 +50,8 @@ module.exports = createClass({
addonID: PropTypes.string.isRequired,
icon: PropTypes.string,
name: PropTypes.string.isRequired,
temporarilyInstalled: PropTypes.bool
temporarilyInstalled: PropTypes.bool,
url: PropTypes.string,
}).isRequired
},
@ -68,6 +88,10 @@ module.exports = createClass({
}),
dom.span({ className: "target-name", title: target.name }, target.name)
),
dom.dl(
{ className: "addon-target-info" },
...filePathForTarget(target),
),
dom.div({className: "addon-target-actions"},
dom.button({
className: "debug-button addon-target-button",

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

@ -20,6 +20,7 @@ support-files =
!/devtools/client/framework/test/shared-head.js
[browser_addons_debug_bootstrapped.js]
[browser_addons_debug_info.js]
[browser_addons_debug_webextension.js]
tags = webextensions
[browser_addons_debug_webextension_inspector.js]

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

@ -0,0 +1,28 @@
"use strict";
const ADDON_ID = "test-devtools@mozilla.org";
const ADDON_NAME = "test-devtools";
add_task(function* () {
let { tab, document } = yield openAboutDebugging("addons");
yield waitForInitialAddonList(document);
yield installAddon({
document,
path: "addons/unpacked/install.rdf",
name: ADDON_NAME,
});
let container = document.querySelector(`[data-addon-id="${ADDON_ID}"]`);
let filePath = container.querySelector(".file-path");
let expectedFilePath = "browser/devtools/client/aboutdebugging/test/addons/unpacked/";
// Verify that the path to the install location is shown next to its label.
ok(filePath, "file path is in DOM");
ok(filePath.textContent.endsWith(expectedFilePath), "file path is set correctly");
is(filePath.previousElementSibling.textContent, "Location", "file path has label");
yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
yield closeAboutDebugging(tab);
});

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

@ -1,33 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the qsa commands work as they should.
const TEST_URI = "data:text/html;charset=utf-8,<body></body>";
function test() {
helpers.addTabWithToolbar(TEST_URI, function (options) {
return helpers.audit(options, [
{
setup: "qsa",
check: {
input: "qsa",
hints: " [query]",
markup: "VVV",
status: "VALID"
}
},
{
setup: "qsa body",
check: {
input: "qsa body",
hints: "",
markup: "VVVVVVVV",
status: "VALID"
}
}
]);
}).then(finish, helpers.handleError);
}
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the qsa commands work as they should.
const TEST_URI = "data:text/html;charset=utf-8,<body></body>";
function test() {
helpers.addTabWithToolbar(TEST_URI, function (options) {
return helpers.audit(options, [
{
setup: "qsa",
check: {
input: "qsa",
hints: " [query]",
markup: "VVV",
status: "VALID"
}
},
{
setup: "qsa body",
check: {
input: "qsa body",
hints: "",
markup: "VVVVVVVV",
status: "VALID"
}
}
]);
}).then(finish, helpers.handleError);
}

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

@ -2,57 +2,57 @@
/* 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/ */
/**
* Make sure that canceling a name change correctly unhides the separator and
* value elements.
*/
const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
function test() {
Task.spawn(function* () {
/**
* Make sure that canceling a name change correctly unhides the separator and
* value elements.
*/
const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
function test() {
Task.spawn(function* () {
let options = {
source: TAB_URL,
line: 1
};
let [tab,, panel] = yield initDebugger(TAB_URL, options);
let win = panel.panelWin;
let vars = win.DebuggerView.Variables;
win.DebuggerView.WatchExpressions.addExpression("this");
callInTab(tab, "ermahgerd");
yield waitForDebuggerEvents(panel, win.EVENTS.FETCHED_WATCH_EXPRESSIONS);
let exprScope = vars.getScopeAtIndex(0);
let {target} = exprScope.get("this");
let name = target.querySelector(".title > .name");
let separator = target.querySelector(".separator");
let value = target.querySelector(".value");
is(separator.hidden, false,
"The separator element should not be hidden.");
is(value.hidden, false,
"The value element should not be hidden.");
for (let key of ["ESCAPE", "RETURN"]) {
EventUtils.sendMouseEvent({ type: "dblclick" }, name, win);
is(separator.hidden, true,
"The separator element should be hidden.");
is(value.hidden, true,
"The value element should be hidden.");
EventUtils.sendKey(key, win);
is(separator.hidden, false,
"The separator element should not be hidden.");
is(value.hidden, false,
"The value element should not be hidden.");
}
yield resumeDebuggerThenCloseAndFinish(panel);
});
}
let [tab,, panel] = yield initDebugger(TAB_URL, options);
let win = panel.panelWin;
let vars = win.DebuggerView.Variables;
win.DebuggerView.WatchExpressions.addExpression("this");
callInTab(tab, "ermahgerd");
yield waitForDebuggerEvents(panel, win.EVENTS.FETCHED_WATCH_EXPRESSIONS);
let exprScope = vars.getScopeAtIndex(0);
let {target} = exprScope.get("this");
let name = target.querySelector(".title > .name");
let separator = target.querySelector(".separator");
let value = target.querySelector(".value");
is(separator.hidden, false,
"The separator element should not be hidden.");
is(value.hidden, false,
"The value element should not be hidden.");
for (let key of ["ESCAPE", "RETURN"]) {
EventUtils.sendMouseEvent({ type: "dblclick" }, name, win);
is(separator.hidden, true,
"The separator element should be hidden.");
is(value.hidden, true,
"The value element should be hidden.");
EventUtils.sendKey(key, win);
is(separator.hidden, false,
"The separator element should not be hidden.");
is(value.hidden, false,
"The value element should not be hidden.");
}
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

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

@ -1,80 +1,80 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests opening the variable inspection popup while stopped at a debugger statement,
* clicking "step in" and verifying that the popup is gone.
*/
const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
let gTab, gPanel, gDebugger;
let actions, gSources, gVariables;
function test() {
let options = {
source: TAB_URL,
line: 1
};
initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
gTab = aTab;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
actions = bindActionCreators(gPanel);
gSources = gDebugger.DebuggerView.Sources;
gVariables = gDebugger.DebuggerView.Variables;
let bubble = gDebugger.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
let testPopupHiding = Task.async(function* () {
yield addBreakpoint();
yield ensureThreadClientState(gPanel, "resumed");
yield pauseDebuggee();
yield openVarPopup(gPanel, { line: 20, ch: 17 });
is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
"The popup should be open with a simple text entry");
// Now we're stopped at a breakpoint with an open popup
// we'll send a keypress and check if the popup closes
executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
// The keypress should cause one resumed event and one paused event
yield waitForThreadEvents(gPanel, "resumed");
yield waitForThreadEvents(gPanel, "paused");
// Here's the state we're actually interested in checking..
checkVariablePopupClosed(bubble);
yield resumeDebuggerThenCloseAndFinish(gPanel);
});
testPopupHiding();
});
}
function addBreakpoint() {
return actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
}
function pauseDebuggee() {
generateMouseClickInTab(gTab, "content.document.querySelector('button')");
// The first 'with' scope should be expanded by default, but the
// variables haven't been fetched yet. This is how 'with' scopes work.
return promise.all([
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
]);
}
function checkVariablePopupClosed(bubble) {
ok(!bubble.contentsShown(),
"When stepping, popup should close and be hidden.");
ok(bubble._tooltip.isEmpty(),
"The variable inspection popup should now be empty.");
ok(!bubble._markedText,
"The marked text in the editor was removed.");
}
registerCleanupFunction(function () {
gTab = null;
gPanel = null;
gDebugger = null;
actions = null;
gSources = null;
gVariables = null;
});
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests opening the variable inspection popup while stopped at a debugger statement,
* clicking "step in" and verifying that the popup is gone.
*/
const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
let gTab, gPanel, gDebugger;
let actions, gSources, gVariables;
function test() {
let options = {
source: TAB_URL,
line: 1
};
initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
gTab = aTab;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
actions = bindActionCreators(gPanel);
gSources = gDebugger.DebuggerView.Sources;
gVariables = gDebugger.DebuggerView.Variables;
let bubble = gDebugger.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
let testPopupHiding = Task.async(function* () {
yield addBreakpoint();
yield ensureThreadClientState(gPanel, "resumed");
yield pauseDebuggee();
yield openVarPopup(gPanel, { line: 20, ch: 17 });
is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
"The popup should be open with a simple text entry");
// Now we're stopped at a breakpoint with an open popup
// we'll send a keypress and check if the popup closes
executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
// The keypress should cause one resumed event and one paused event
yield waitForThreadEvents(gPanel, "resumed");
yield waitForThreadEvents(gPanel, "paused");
// Here's the state we're actually interested in checking..
checkVariablePopupClosed(bubble);
yield resumeDebuggerThenCloseAndFinish(gPanel);
});
testPopupHiding();
});
}
function addBreakpoint() {
return actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
}
function pauseDebuggee() {
generateMouseClickInTab(gTab, "content.document.querySelector('button')");
// The first 'with' scope should be expanded by default, but the
// variables haven't been fetched yet. This is how 'with' scopes work.
return promise.all([
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
]);
}
function checkVariablePopupClosed(bubble) {
ok(!bubble.contentsShown(),
"When stepping, popup should close and be hidden.");
ok(bubble._tooltip.isEmpty(),
"The variable inspection popup should now be empty.");
ok(!bubble._markedText,
"The marked text in the editor was removed.");
}
registerCleanupFunction(function () {
gTab = null;
gPanel = null;
gDebugger = null;
actions = null;
gSources = null;
gVariables = null;
});

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

@ -79,6 +79,10 @@ reload = Reload
# disabled 'reload' button.
reloadDisabledTooltip = Only temporarily installed add-ons can be reloaded
# LOCALIZATION NOTE (location):
# This string is displayed as a label for the filesystem location of an extension.
location = Location
# LOCALIZATION NOTE (workers):
# This string is displayed as a header of the about:debugging#workers page.
workers = Workers

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

@ -50,6 +50,10 @@
/* General */
* {
box-sizing: border-box;
}
html,
body,
#mount,
@ -63,26 +67,18 @@ body,
overflow: hidden;
}
* {
box-sizing: border-box;
}
.split-box {
overflow: hidden;
}
.toolbar-labels {
overflow: hidden;
display: flex;
flex: auto;
}
/* Toolbar */
.devtools-toolbar {
display: flex;
}
.devtools-toolbar-container.devtools-toolbar {
height: auto !important;
.devtools-toolbar-container {
height: auto;
flex-wrap: wrap;
justify-content: space-between;
}
@ -94,13 +90,9 @@ body,
align-items: center;
}
#response-content-image-box {
overflow: auto;
}
.cropped-textbox .textbox-input {
/* workaround for textbox not supporting the @crop attribute */
text-overflow: ellipsis;
.requests-list-filter-buttons {
display: flex;
flex-wrap: nowrap;
}
.learn-more-link {
@ -116,11 +108,13 @@ body,
/* Status bar */
.devtools-status-bar-label {
flex: 0;
}
.status-bar-label {
display: inline-flex;
align-content: stretch;
margin-inline-end: 10px;
/* Status bar has just one line so, don't wrap labels */
white-space: nowrap;
}
@ -143,15 +137,7 @@ body,
color: red;
}
/* Request list */
.request-list-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
}
/* Request list empty panel */
.request-list-empty-notice {
margin: 0;
@ -184,75 +170,86 @@ body,
margin: 0 5px;
}
/* Network requests table */
/* Requests list table */
.requests-list-toolbar {
.request-list-container {
display: flex;
padding: 0;
flex-direction: column;
width: 100%;
height: 100%;
overflow-x: hidden;
}
.requests-list-filter-buttons {
display: flex;
flex-wrap: nowrap;
.requests-list-wrapper {
width: 100%;
height: 100%;
}
.theme-firebug .requests-list-toolbar {
height: 19px !important;
.requests-list-table {
display: table;
position: relative;
width: 100%;
height: 100%;
}
.requests-list-contents {
height: 100%;
display: table-row-group;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow-x: hidden;
overflow-y: auto;
--timings-scale: 1;
--timings-rev-scale: 1;
}
.requests-list-subitem {
display: flex;
flex: none;
box-sizing: border-box;
align-items: center;
padding: 3px;
.requests-list-column {
display: table-cell;
cursor: default;
}
.subitem-label {
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
max-width: 50px;
min-width: 50px;
}
/* Requests list header */
.requests-list-column > * {
display: inline-block;
}
.requests-list-header {
display: flex;
flex: none;
.theme-firebug .requests-list-column {
padding: 1px;
}
/* Requests list headers */
.requests-list-headers {
display: table-header-group;
height: 24px;
padding: 0;
}
.requests-list-headers .requests-list-column:first-child .requests-list-header-button {
border-width: 0;
}
.requests-list-header-button {
display: flex;
align-items: center;
flex: auto;
-moz-appearance: none; appearance: none;
background-color: transparent;
border-image: linear-gradient(transparent 15%,
var(--theme-splitter-color) 15%,
var(--theme-splitter-color) 85%,
transparent 85%) 1 1;
border-style: solid;
border-width: 0;
border-inline-start-width: 1px;
min-width: 1px;
min-height: 24px;
margin: 0;
padding-top: 2px;
padding-bottom: 2px;
padding-inline-start: 16px;
padding-inline-end: 0;
width: 100%;
min-height: 23px;
text-align: center;
color: inherit;
font-weight: inherit !important;
}
.requests-list-header-button::-moz-focus-inner {
@ -260,27 +257,27 @@ body,
padding: 0;
}
.requests-list-header:first-child .requests-list-header-button {
border-width: 0;
}
.requests-list-header-button:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.requests-list-header-button > .button-text {
flex: auto;
white-space: nowrap;
display: inline-block;
text-align: center;
vertical-align: middle;
/* Align button text to center */
width: calc(100% - 8px);
overflow: hidden;
text-overflow: ellipsis;
}
.requests-list-header-button > .button-icon {
flex: none;
display: inline-block;
width: 7px;
height: 4px;
margin-inline-start: 3px;
margin-inline-end: 6px;
width: 7px;
vertical-align: middle;
}
.requests-list-header-button[data-sorted=ascending] > .button-icon {
@ -291,10 +288,6 @@ body,
background-image: var(--sort-descending-image);
}
.requests-list-waterfall-label-wrapper {
display: flex;
}
.requests-list-header-button[data-sorted],
.requests-list-header-button[data-sorted]:hover {
background-color: var(--theme-selection-background);
@ -302,13 +295,11 @@ body,
}
.requests-list-header-button[data-sorted],
.requests-list-header[data-active] + .requests-list-header .requests-list-header-button {
.requests-list-column[data-active] + .requests-list-column .requests-list-header-button {
border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1;
}
/* Firebug theme support for Network panel header */
.theme-firebug .requests-list-header {
.theme-firebug .requests-list-headers {
padding: 0 !important;
font-weight: bold;
background: linear-gradient(rgba(255, 255, 255, 0.05),
@ -333,126 +324,26 @@ body,
color: inherit !important;
}
.theme-firebug .requests-list-header:hover:active {
.theme-firebug .requests-list-header-button:hover:active {
background-image: linear-gradient(rgba(0, 0, 0, 0.1),
transparent);
}
/* Requests list column */
/* Network requests table: specific column dimensions */
/* Status column */
.requests-list-status {
max-width: 6em;
text-align: center;
width: 8vw;
width: 8%;
}
.requests-list-method,
.requests-list-method-box {
max-width: 7em;
text-align: center;
width: 10vw;
}
.requests-list-icon-and-file {
width: 22vw;
}
.requests-list-icon {
background: transparent;
width: 15px;
height: 15px;
margin-inline-end: 4px;
}
.requests-list-icon {
outline: 1px solid var(--table-splitter-color);
}
.requests-list-security-and-domain {
width: 14vw;
}
.requests-list-remoteip {
width: 8vw;
}
.requests-list-protocol {
width: 7vw;
}
.requests-security-state-icon {
flex: none;
width: 16px;
height: 16px;
margin-inline-end: 4px;
}
.request-list-item.selected .requests-security-state-icon {
filter: brightness(1.3);
}
.security-state-insecure {
background-image: url(chrome://devtools/skin/images/security-state-insecure.svg);
}
.security-state-secure {
background-image: url(chrome://devtools/skin/images/security-state-secure.svg);
}
.security-state-weak {
background-image: url(chrome://devtools/skin/images/security-state-weak.svg);
}
.security-state-broken {
background-image: url(chrome://devtools/skin/images/security-state-broken.svg);
}
.security-state-local {
background-image: url(chrome://devtools/skin/images/globe.svg);
}
.requests-list-type,
.requests-list-size {
max-width: 6em;
width: 8vw;
justify-content: center;
}
.requests-list-transferred {
max-width: 8em;
width: 8vw;
justify-content: center;
}
.requests-list-cause {
max-width: 8em;
width: 8vw;
}
.requests-list-cause-stack {
background-color: var(--theme-body-color-alt);
color: var(--theme-body-background);
font-size: 8px;
.theme-firebug .requests-list-status {
font-weight: bold;
line-height: 10px;
border-radius: 3px;
padding: 0 2px;
margin: 0;
margin-inline-end: 3px;
-moz-user-select: none;
}
.request-list-item.selected .requests-list-transferred.theme-comment {
color: var(--theme-selection-color);
}
/* Network requests table: status codes */
.requests-list-status-code {
margin-inline-start: 3px !important;
margin-inline-start: 3px;
width: 3em;
margin-inline-end: -3em !important;
}
.requests-list-status-icon {
@ -463,7 +354,6 @@ body,
margin-inline-end: 5px;
border-radius: 10px;
transition: box-shadow 0.5s ease-in-out;
box-sizing: border-box;
}
.request-list-item.selected .requests-list-status-icon {
@ -500,7 +390,7 @@ body,
/* 4xx and 5xx are squares - error codes */
.requests-list-status-icon[data-code^="4"] {
background-color: var(--theme-highlight-red);
background-color: var(--theme-highlight-red);
border-radius: 0; /* squares */
}
@ -510,27 +400,171 @@ body,
transform: rotate(45deg);
}
/* Network requests table: waterfall header */
/* Method column */
.requests-list-method {
width: 8%;
}
.theme-firebug .requests-list-method {
color: rgb(128, 128, 128);
}
/* File column */
.requests-list-file {
width: 22%;
}
.requests-list-file.requests-list-column {
text-align: start;
}
.requests-list-icon {
background: transparent;
width: 15px;
height: 15px;
margin: 0 4px;
outline: 1px solid var(--table-splitter-color);
vertical-align: top;
}
/* Protocol column */
.requests-list-protocol {
width: 8%;
}
/* Domain column */
.requests-list-domain {
width: 13%;
}
.requests-list-domain.requests-list-column {
text-align: start;
}
.requests-security-state-icon {
display: inline-block;
width: 16px;
height: 16px;
margin: 0 4px;
vertical-align: top;
}
.request-list-item.selected .requests-security-state-icon {
filter: brightness(1.3);
}
.security-state-insecure {
background-image: url(chrome://devtools/skin/images/security-state-insecure.svg);
}
.security-state-secure {
background-image: url(chrome://devtools/skin/images/security-state-secure.svg);
}
.security-state-weak {
background-image: url(chrome://devtools/skin/images/security-state-weak.svg);
}
.security-state-broken {
background-image: url(chrome://devtools/skin/images/security-state-broken.svg);
}
.security-state-local {
background-image: url(chrome://devtools/skin/images/globe.svg);
}
/* RemoteIP column */
.requests-list-remoteip {
width: 9%;
}
/* Cause column */
.requests-list-cause {
width: 9%;
}
.requests-list-cause-stack {
display: inline-block;
background-color: var(--theme-body-color-alt);
color: var(--theme-body-background);
font-size: 8px;
font-weight: bold;
line-height: 10px;
border-radius: 3px;
padding: 0 2px;
margin: 0;
margin-inline-end: 3px;
}
/* Type column */
.requests-list-type {
width: 6%;
}
/* Transferred column */
.requests-list-transferred {
width: 9%;
}
/* Size column */
.requests-list-size {
width: 7%;
}
.theme-firebug .requests-list-size {
justify-content: end;
padding-inline-end: 4px;
}
/* Waterfall column */
.requests-list-waterfall {
flex: auto;
width: 20vw;
max-width: 20vw;
min-width: 20vw;
background-repeat: repeat-y;
background-position: left center;
/* Background created on a <canvas> in js. */
/* @see devtools/client/netmonitor/src/waterfall-background.js */
background-image: -moz-element(#waterfall-background);
}
.requests-list-waterfall:dir(rtl) {
background-position: right center;
}
.requests-list-waterfall > .requests-list-header-button {
padding-inline-start: 0;
}
.requests-list-waterfall > .requests-list-header-button > .button-text {
width: auto;
}
.requests-list-waterfall-label-wrapper:not(.requests-list-waterfall-visible) {
padding-inline-start: 16px;
}
.requests-list-timings-division {
padding-top: 2px;
display: inline-block;
padding-inline-start: 4px;
font-size: 75%;
pointer-events: none;
box-sizing: border-box;
text-align: start;
/* Allow the timing label to shrink if the container gets too narrow.
* The container width then is not limited by the content. */
flex: initial;
}
:root[platform="win"] .requests-list-timings-division {
padding-top: 1px;
font-size: 90%;
}
.requests-list-timings-division:not(:first-child) {
@ -558,22 +592,6 @@ body,
font-weight: 600;
}
/* Network requests table: waterfall items */
.requests-list-subitem.requests-list-waterfall {
padding-inline-start: 0;
padding-inline-end: 4px;
/* Background created on a <canvas> in js. */
/* @see devtools/client/netmonitor/netmonitor-view.js */
background-image: -moz-element(#waterfall-background);
background-repeat: repeat-y;
background-position: left center;
}
.requests-list-subitem.requests-list-waterfall:dir(rtl) {
background-position: right center;
}
.requests-list-timings {
display: flex;
flex: none;
@ -589,24 +607,6 @@ body,
transform-origin: right center;
}
.requests-list-timings-total:dir(ltr) {
transform-origin: left center;
}
.requests-list-timings-total:dir(rtl) {
transform-origin: right center;
}
.requests-list-timings-total {
display: inline-block;
padding-inline-start: 4px;
font-size: 85%;
font-weight: 600;
white-space: nowrap;
/* This node should not be scaled - apply a reversed transformation */
transform: scaleX(var(--timings-rev-scale));
}
.requests-list-timings-box {
display: inline-block;
height: 9px;
@ -641,17 +641,29 @@ body,
background-color: var(--timing-receive-color);
}
/* SideMenuWidget */
#network-table .request-list-empty-notice,
#network-table .request-list-container {
background-color: var(--theme-body-background);
.requests-list-timings-total {
display: inline-block;
padding-inline-start: 4px;
font-size: 85%;
font-weight: 600;
white-space: nowrap;
/* This node should not be scaled - apply a reversed transformation */
transform: scaleX(var(--timings-rev-scale));
}
.requests-list-timings-total:dir(ltr) {
transform-origin: left center;
}
.requests-list-timings-total:dir(rtl) {
transform-origin: right center;
}
/* Request list item */
.request-list-item {
display: flex;
border-top-color: transparent;
border-bottom-color: transparent;
padding: 0;
display: table-row;
height: 24px;
}
.request-list-item.selected {
@ -667,40 +679,15 @@ body,
background-color: var(--theme-selection-background-semitransparent);
}
.request-list-item.fromCache > .requests-list-subitem:not(.requests-list-waterfall) {
opacity: 0.6;
.request-list-item.fromCache > .requests-list-column:not(.requests-list-waterfall) {
opacity: 0.6;
}
.theme-firebug .request-list-item:not(.selected):hover {
background: #EFEFEF;
}
.theme-firebug .requests-list-subitem {
padding: 1px;
}
/* HTTP Status Column */
.theme-firebug .requests-list-subitem.requests-list-status {
font-weight: bold;
}
/* Method Column */
.theme-firebug .requests-list-subitem.requests-list-method-box {
color: rgb(128, 128, 128);
}
.request-list-item.selected .requests-list-method {
color: var(--theme-selection-color);
}
/* Size Column */
.theme-firebug .requests-list-subitem.requests-list-size {
justify-content: end;
padding-inline-end: 4px;
}
/* Network details panel */
/* Network details panel toggle */
.network-details-panel-toggle[disabled] {
display: none;
@ -716,7 +703,7 @@ body,
background-image: var(--theme-pane-expand-image);
}
/* Network request details tabpanels */
/* Network details panel */
.network-details-panel {
width: 100%;
@ -916,7 +903,6 @@ body,
height: 50vh;
font: message-box;
resize: none;
box-sizing: border-box;
}
.headers-summary .raw-headers .tabpanel-summary-label {
@ -1052,7 +1038,7 @@ body,
}
}
/* Custom request view */
/* Custom request panel */
.custom-request-panel {
height: 100%;
@ -1101,22 +1087,23 @@ body,
margin-inline-start: 6px;
}
/* Performance analysis buttons */
/* Statistics panel buttons */
.requests-list-network-summary-button {
display: flex;
flex-wrap: nowrap;
align-items: center;
display: inline-flex;
cursor: pointer;
height: 18px;
background: none;
box-shadow: none;
border-color: transparent;
padding-inline-end: 0;
cursor: pointer;
margin-top: 3px;
margin-bottom: 3px;
margin-inline-end: 1em;
}
.requests-list-network-summary-button > .summary-info-icon {
background-image: url(chrome://devtools/skin/images/profiler-stopwatch.svg);
background: url(chrome://devtools/skin/images/profiler-stopwatch.svg) no-repeat;
filter: var(--icon-filter);
width: 16px;
height: 16px;
@ -1127,7 +1114,7 @@ body,
opacity: 1;
}
/* Performance analysis view */
/* Statistics panel */
.statistics-panel {
display: flex;
@ -1263,7 +1250,7 @@ body,
flex-direction: column;
}
/* Firebug theme support for network charts */
/* Firebug theme support for statistics panel charts */
.theme-firebug .chart-colored-blob[name=html] {
fill: rgba(94, 136, 176, 0.8); /* Blue-Grey highlight */
@ -1305,49 +1292,20 @@ body,
background: rgba(84, 235, 159, 0.8);
}
/* Responsive sidebar */
/* Responsive web design support */
@media (max-width: 700px) {
.requests-list-toolbar {
height: 22px;
}
.requests-list-header-button {
min-height: 22px;
padding-left: 8px;
}
.requests-list-status {
max-width: none;
padding-inline-start: 8px;
}
.requests-list-status-code {
width: auto;
}
.requests-list-method,
.requests-list-method-box {
max-width: none;
width: 12vw;
}
.requests-list-icon-and-file {
width: 22vw;
}
.requests-list-security-and-domain {
width: 16vw;
}
.requests-list-remoteip {
width: 8vw;
}
.requests-list-cause,
.requests-list-type,
.requests-list-transferred,
.requests-list-size {
max-width: none;
width: 8vw;
/* Given a fix max-width to display all columns in RWD mode */
max-width: 7%;
}
.requests-list-waterfall {
@ -1364,26 +1322,9 @@ body,
width: 100%;
height: 1px;
}
}
/* Platform overrides (copied in from the old platform specific files) */
:root[platform="win"] .requests-list-timings-division {
padding-top: 1px;
font-size: 90%;
}
:root[platform="linux"] #headers-summary-resend {
padding: 4px;
}
:root[platform="linux"] #toggle-raw-headers {
padding: 4px;
}
/* Responsive sidebar */
@media (max-width: 700px) {
:root[platform="linux"] .requests-list-header-button {
font-size: 85%;
}
}

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

@ -10,7 +10,7 @@ const {
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { div, span } = DOM;
const { div } = DOM;
const RequestListColumnCause = createClass({
displayName: "RequestListColumnCause",
@ -25,35 +25,27 @@ const RequestListColumnCause = createClass({
},
render() {
const {
item,
let {
item: { cause },
onCauseBadgeClick,
} = this.props;
const { cause } = item;
let causeType = "";
let causeUri = undefined;
let causeType = "unknown";
let causeHasStack = false;
if (cause) {
// Legacy server might send a numeric value. Display it as "unknown"
causeType = typeof cause.type === "string" ? cause.type : "unknown";
causeUri = cause.loadingDocumentUri;
causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
}
return (
div({
className: "requests-list-subitem requests-list-cause",
title: causeUri,
},
span({
div({ className: "requests-list-column requests-list-cause", title: causeType },
causeHasStack && div({
className: "requests-list-cause-stack",
hidden: !causeHasStack,
onClick: onCauseBadgeClick,
}, "JS"),
span({ className: "subitem-label" }, causeType),
causeType
)
);
}

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

@ -11,7 +11,7 @@ const {
} = require("devtools/client/shared/vendor/react");
const { getFormattedSize } = require("../utils/format-utils");
const { div, span } = DOM;
const { div } = DOM;
const RequestListColumnContentSize = createClass({
displayName: "RequestListColumnContentSize",
@ -25,20 +25,10 @@ const RequestListColumnContentSize = createClass({
},
render() {
const { contentSize } = this.props.item;
let text;
if (typeof contentSize == "number") {
text = getFormattedSize(contentSize);
}
let { contentSize } = this.props.item;
let size = typeof contentSize === "number" ? getFormattedSize(contentSize) : null;
return (
div({
className: "requests-list-subitem subitem-label requests-list-size",
title: text,
},
span({ className: "subitem-label" }, text),
)
div({ className: "requests-list-column requests-list-size", title: size }, size)
);
}
});

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

@ -12,12 +12,12 @@ const {
const { L10N } = require("../utils/l10n");
const { propertiesEqual } = require("../utils/request-utils");
const { div, span } = DOM;
const { div } = DOM;
const UPDATED_DOMAIN_PROPS = [
"urlDetails",
"remoteAddress",
"securityState",
"urlDetails",
];
const RequestListColumnDomain = createClass({
@ -33,12 +33,13 @@ const RequestListColumnDomain = createClass({
},
render() {
const { item, onSecurityIconClick } = this.props;
const { urlDetails, remoteAddress, securityState } = item;
let { item, onSecurityIconClick } = this.props;
let { remoteAddress, securityState, urlDetails: { host, isLocal } } = item;
let iconClassList = ["requests-security-state-icon"];
let iconTitle;
if (urlDetails.isLocal) {
let title = host + (remoteAddress ? ` (${remoteAddress})` : "");
if (isLocal) {
iconClassList.push("security-state-local");
iconTitle = L10N.getStr("netmonitor.security.state.secure");
} else if (securityState) {
@ -46,16 +47,14 @@ const RequestListColumnDomain = createClass({
iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`);
}
let title = urlDetails.host + (remoteAddress ? ` (${remoteAddress})` : "");
return (
div({ className: "requests-list-subitem requests-list-security-and-domain" },
div({ className: "requests-list-column requests-list-domain", title },
div({
className: iconClassList.join(" "),
onMouseDown: onSecurityIconClick,
title: iconTitle,
onClick: onSecurityIconClick,
}),
span({ className: "subitem-label requests-list-domain", title }, urlDetails.host),
host,
)
);
}

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

@ -14,8 +14,8 @@ const { propertiesEqual } = require("../utils/request-utils");
const { div, img } = DOM;
const UPDATED_FILE_PROPS = [
"urlDetails",
"responseContentDataUri",
"urlDetails",
];
const RequestListColumnFile = createClass({
@ -30,22 +30,18 @@ const RequestListColumnFile = createClass({
},
render() {
const { urlDetails, responseContentDataUri } = this.props.item;
let { responseContentDataUri, urlDetails } = this.props.item;
return (
div({ className: "requests-list-subitem requests-list-icon-and-file" },
div({
className: "requests-list-column requests-list-file",
title: urlDetails.unicodeUrl,
},
img({
className: "requests-list-icon",
src: responseContentDataUri,
hidden: !responseContentDataUri,
"data-type": responseContentDataUri ? "thumbnail" : undefined,
}),
div({
className: "subitem-label requests-list-file",
title: urlDetails.unicodeUrl,
},
urlDetails.baseNameWithQuery,
),
urlDetails.baseNameWithQuery
)
);
}

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

@ -10,7 +10,7 @@ const {
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { div, span } = DOM;
const { div } = DOM;
const RequestListColumnMethod = createClass({
displayName: "RequestListColumnMethod",
@ -24,12 +24,8 @@ const RequestListColumnMethod = createClass({
},
render() {
const { method } = this.props.item;
return (
div({ className: "requests-list-subitem requests-list-method-box" },
span({ className: "subitem-label requests-list-method" }, method)
)
);
let { method } = this.props.item;
return div({ className: "requests-list-column requests-list-method" }, method);
}
});

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

@ -10,7 +10,7 @@ const {
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { div, span } = DOM;
const { div } = DOM;
const RequestListColumnProtocol = createClass({
displayName: "RequestListColumnProtocol",
@ -24,10 +24,13 @@ const RequestListColumnProtocol = createClass({
},
render() {
const { httpVersion } = this.props.item;
let { httpVersion = "" } = this.props.item;
return (
div({ className: "requests-list-subitem requests-list-protocol" },
span({ className: "subitem-label", title: httpVersion }, httpVersion),
div({
className: "requests-list-column requests-list-protocol",
title: httpVersion,
},
httpVersion
)
);
}

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

@ -10,7 +10,7 @@ const {
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { div, span } = DOM;
const { div } = DOM;
const RequestListColumnRemoteIP = createClass({
displayName: "RequestListColumnRemoteIP",
@ -24,12 +24,12 @@ const RequestListColumnRemoteIP = createClass({
},
render() {
const { remoteAddress, remotePort } = this.props.item;
let remoteSummary = remoteAddress ? `${remoteAddress}:${remotePort}` : "";
let { remoteAddress, remotePort } = this.props.item;
let remoteIP = remoteAddress ? `${remoteAddress}:${remotePort}` : "unknown";
return (
div({ className: "requests-list-subitem requests-list-remoteip" },
span({ className: "subitem-label", title: remoteSummary }, remoteSummary),
div({ className: "requests-list-column requests-list-remoteip", title: remoteIP },
remoteIP
)
);
}

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

@ -12,7 +12,7 @@ const {
const { L10N } = require("../utils/l10n");
const { propertiesEqual } = require("../utils/request-utils");
const { div, span } = DOM;
const { div } = DOM;
const UPDATED_STATUS_PROPS = [
"fromCache",
@ -33,8 +33,7 @@ const RequestListColumnStatus = createClass({
},
render() {
const { status, statusText, fromCache, fromServiceWorker } = this.props.item;
let { fromCache, fromServiceWorker, status, statusText } = this.props.item;
let code, title;
if (status) {
@ -64,9 +63,9 @@ const RequestListColumnStatus = createClass({
}
return (
div({ className: "requests-list-subitem requests-list-status", title },
div({ className: "requests-list-column requests-list-status", title },
div({ className: "requests-list-status-icon", "data-code": code }),
span({ className: "subitem-label requests-list-status-code" }, status)
div({ className: "requests-list-status-code" }, status)
)
);
}

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

@ -13,7 +13,7 @@ const { getFormattedSize } = require("../utils/format-utils");
const { L10N } = require("../utils/l10n");
const { propertiesEqual } = require("../utils/request-utils");
const { div, span } = DOM;
const { div } = DOM;
const UPDATED_TRANSFERRED_PROPS = [
"transferredSize",
@ -33,16 +33,13 @@ const RequestListColumnTransferredSize = createClass({
},
render() {
const { transferredSize, fromCache, fromServiceWorker, status } = this.props.item;
let { fromCache, fromServiceWorker, status, transferredSize } = this.props.item;
let text;
let className = "subitem-label";
if (fromCache || status === "304") {
text = L10N.getStr("networkMenu.sizeCached");
className += " theme-comment";
} else if (fromServiceWorker) {
text = L10N.getStr("networkMenu.sizeServiceWorker");
className += " theme-comment";
} else if (typeof transferredSize == "number") {
text = getFormattedSize(transferredSize);
} else if (transferredSize === null) {
@ -50,11 +47,8 @@ const RequestListColumnTransferredSize = createClass({
}
return (
div({
className: "requests-list-subitem requests-list-transferred",
title: text,
},
span({ className }, text),
div({ className: "requests-list-column requests-list-transferred", title: text },
text
)
);
}

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

@ -11,7 +11,7 @@ const {
} = require("devtools/client/shared/vendor/react");
const { getAbbreviatedMimeType } = require("../utils/request-utils");
const { div, span } = DOM;
const { div } = DOM;
const RequestListColumnType = createClass({
displayName: "RequestListColumnType",
@ -25,18 +25,19 @@ const RequestListColumnType = createClass({
},
render() {
const { mimeType } = this.props.item;
let { mimeType } = this.props.item;
let abbrevType;
if (mimeType) {
abbrevType = getAbbreviatedMimeType(mimeType);
}
return (
div({
className: "requests-list-subitem requests-list-type",
className: "requests-list-column requests-list-type",
title: mimeType,
},
span({ className: "subitem-label" }, abbrevType),
abbrevType
)
);
}

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

@ -16,10 +16,12 @@ const { div } = DOM;
const UPDATED_WATERFALL_PROPS = [
"eventTimings",
"totalTime",
"fromCache",
"fromServiceWorker",
"totalTime",
];
// List of properties of the timing info we want to create boxes for
const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
const RequestListColumnWaterfall = createClass({
displayName: "RequestListColumnWaterfall",
@ -30,15 +32,15 @@ const RequestListColumnWaterfall = createClass({
},
shouldComponentUpdate(nextProps) {
return this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis ||
!propertiesEqual(UPDATED_WATERFALL_PROPS, this.props.item, nextProps.item);
return !propertiesEqual(UPDATED_WATERFALL_PROPS, this.props.item, nextProps.item) ||
this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis;
},
render() {
const { item, firstRequestStartedMillis } = this.props;
let { firstRequestStartedMillis, item } = this.props;
return (
div({ className: "requests-list-subitem requests-list-waterfall" },
div({ className: "requests-list-column requests-list-waterfall" },
div({
className: "requests-list-timings",
style: {
@ -52,11 +54,8 @@ const RequestListColumnWaterfall = createClass({
}
});
// List of properties of the timing info we want to create boxes for
const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
function timingBoxes(item) {
const { eventTimings, totalTime, fromCache, fromServiceWorker } = item;
let { eventTimings, fromCache, fromServiceWorker, totalTime } = item;
let boxes = [];
if (fromCache || fromServiceWorker) {
@ -71,22 +70,26 @@ function timingBoxes(item) {
// Don't render anything if it surely won't be visible.
// One millisecond == one unscaled pixel.
if (width > 0) {
boxes.push(div({
key,
className: "requests-list-timings-box " + key,
style: { width }
}));
boxes.push(
div({
key,
className: `requests-list-timings-box ${key}`,
style: { width },
})
);
}
}
}
if (typeof totalTime === "number") {
let text = L10N.getFormatStr("networkMenu.totalMS", totalTime);
boxes.push(div({
key: "total",
className: "requests-list-timings-total",
title: text
}, text));
let title = L10N.getFormatStr("networkMenu.totalMS", totalTime);
boxes.push(
div({
key: "total",
className: "requests-list-timings-total",
title,
}, title)
);
}
return boxes;

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

@ -154,7 +154,7 @@ const RequestListContent = createClass({
return false;
}
if (requestItem.responseContent && target.closest(".requests-list-icon-and-file")) {
if (requestItem.responseContent && target.closest(".requests-list-icon")) {
return setTooltipImageContent(tooltip, itemEl, requestItem);
}
@ -224,33 +224,37 @@ const RequestListContent = createClass({
columns,
displayedRequests,
firstRequestStartedMillis,
selectedRequestId,
onCauseBadgeClick,
onItemMouseDown,
onSecurityIconClick,
selectedRequestId,
} = this.props;
return (
div({
ref: "contentEl",
className: "requests-list-contents",
tabIndex: 0,
onKeyDown: this.onKeyDown,
},
displayedRequests.map((item, index) => RequestListItem({
firstRequestStartedMillis,
fromCache: item.status === "304" || item.fromCache,
columns,
item,
index,
isSelected: item.id === selectedRequestId,
key: item.id,
onContextMenu: this.onContextMenu,
onFocusedNodeChange: this.onFocusedNodeChange,
onMouseDown: () => onItemMouseDown(item.id),
onCauseBadgeClick: () => onCauseBadgeClick(item.cause),
onSecurityIconClick: () => onSecurityIconClick(item.securityState),
}))
div({ className: "requests-list-wrapper"},
div({ className: "requests-list-table"},
div({
ref: "contentEl",
className: "requests-list-contents",
tabIndex: 0,
onKeyDown: this.onKeyDown,
},
displayedRequests.map((item, index) => RequestListItem({
firstRequestStartedMillis,
fromCache: item.status === "304" || item.fromCache,
columns,
item,
index,
isSelected: item.id === selectedRequestId,
key: item.id,
onContextMenu: this.onContextMenu,
onFocusedNodeChange: this.onFocusedNodeChange,
onMouseDown: () => onItemMouseDown(item.id),
onCauseBadgeClick: () => onCauseBadgeClick(item.cause),
onSecurityIconClick: () => onSecurityIconClick(item.securityState),
}))
)
)
)
);
},

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

@ -11,18 +11,15 @@ const {
} = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const Actions = require("../actions/index");
const { HEADERS, REQUESTS_WATERFALL } = require("../constants");
const { getWaterfallScale } = require("../selectors/index");
const { getFormattedTime } = require("../utils/format-utils");
const { HEADERS } = require("../constants");
const { L10N } = require("../utils/l10n");
const WaterfallBackground = require("../waterfall-background");
const RequestListHeaderContextMenu = require("../request-list-header-context-menu");
const { div, button } = DOM;
const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60; // px
/**
* Render the request list header with sorting arrows for columns.
* Displays tick marks in the waterfall column header.
@ -33,19 +30,20 @@ const RequestListHeader = createClass({
propTypes: {
columns: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
sort: PropTypes.object,
scale: PropTypes.number,
waterfallWidth: PropTypes.number,
onHeaderClick: PropTypes.func.isRequired,
resetColumns: PropTypes.func.isRequired,
resizeWaterfall: PropTypes.func.isRequired,
scale: PropTypes.number,
sort: PropTypes.object,
sortBy: PropTypes.func.isRequired,
toggleColumn: PropTypes.func.isRequired,
waterfallWidth: PropTypes.number,
},
componentWillMount() {
const { dispatch } = this.props;
const { resetColumns, toggleColumn } = this.props;
this.contextMenu = new RequestListHeaderContextMenu({
toggleColumn: (column) => dispatch(Actions.toggleColumn(column)),
resetColumns: () => dispatch(Actions.resetColumns()),
resetColumns,
toggleColumn,
});
},
@ -73,27 +71,28 @@ const RequestListHeader = createClass({
},
resizeWaterfall() {
// Measure its width and update the 'waterfallWidth' property in the store.
// The 'waterfallWidth' will be further updated on every window resize.
setTimeout(() => {
let { width } = this.refs.header.getBoundingClientRect();
this.props.resizeWaterfall(width);
}, 50);
let waterfallHeader = this.refs.waterfallHeader;
if (waterfallHeader) {
// Measure its width and update the 'waterfallWidth' property in the store.
// The 'waterfallWidth' will be further updated on every window resize.
setTimeout(() => {
this.props.resizeWaterfall(waterfallHeader.getBoundingClientRect().width);
}, 500);
}
},
render() {
const { sort, scale, waterfallWidth, onHeaderClick, columns } = this.props;
return div(
{ className: "devtools-toolbar requests-list-toolbar" },
div({ className: "toolbar-labels" },
HEADERS.filter(h => columns.get(h.name)).map(header => {
const name = header.name;
const boxName = header.boxName || name;
const label = L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
let { columns, scale, sort, sortBy, waterfallWidth } = this.props;
return (
div({ className: "devtools-toolbar requests-list-headers" },
HEADERS.filter((header) => columns.get(header.name)).map((header) => {
let name = header.name;
let boxName = header.boxName || name;
let label = L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
let sorted, sortedTitle;
const active = sort.type == name ? true : undefined;
let active = sort.type == name ? true : undefined;
if (active) {
sorted = sort.ascending ? "ascending" : "descending";
sortedTitle = L10N.getStr(sort.ascending
@ -101,27 +100,28 @@ const RequestListHeader = createClass({
: "networkMenu.sortedDesc");
}
return div(
{
return (
div({
id: `requests-list-${boxName}-header-box`,
className: `requests-list-header requests-list-${boxName}`,
className: `requests-list-column requests-list-${boxName}`,
key: name,
ref: "header",
ref: `${name}Header`,
// Used to style the next column.
"data-active": active,
onContextMenu: this.onContextMenu,
},
button(
{
button({
id: `requests-list-${name}-button`,
className: `requests-list-header-button requests-list-${name}`,
className: `requests-list-header-button`,
"data-sorted": sorted,
title: sortedTitle,
onClick: () => onHeaderClick(name),
onClick: () => sortBy(name),
},
name == "waterfall" ? WaterfallLabel(waterfallWidth, scale, label)
: div({ className: "button-text" }, label),
div({ className: "button-icon" })
name === "waterfall"
? WaterfallLabel(waterfallWidth, scale, label)
: div({ className: "button-text" }, label),
div({ className: "button-icon" })
)
)
);
})
@ -137,11 +137,11 @@ function waterfallDivisionLabels(waterfallWidth, scale) {
let labels = [];
// Build new millisecond tick labels...
let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE;
let timingStep = REQUESTS_WATERFALL.HEADER_TICKS_MULTIPLE;
let scaledStep = scale * timingStep;
// Ignore any divisions that would end up being too close to each other.
while (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
while (scaledStep < REQUESTS_WATERFALL.HEADER_TICKS_SPACING_MIN) {
scaledStep *= 2;
}
@ -185,7 +185,7 @@ function waterfallDivisionLabels(waterfallWidth, scale) {
function WaterfallLabel(waterfallWidth, scale, label) {
let className = "button-text requests-list-waterfall-label-wrapper";
if (waterfallWidth != null && scale != null) {
if (waterfallWidth !== null && scale !== null) {
label = waterfallDivisionLabels(waterfallWidth, scale);
className += " requests-list-waterfall-visible";
}
@ -194,17 +194,18 @@ function WaterfallLabel(waterfallWidth, scale, label) {
}
module.exports = connect(
state => ({
(state) => ({
columns: state.ui.columns,
sort: state.sort,
scale: getWaterfallScale(state),
waterfallWidth: state.ui.waterfallWidth,
firstRequestStartedMillis: state.requests.firstStartedMillis,
scale: getWaterfallScale(state),
sort: state.sort,
timingMarkers: state.timingMarkers,
waterfallWidth: state.ui.waterfallWidth,
}),
dispatch => ({
dispatch,
onHeaderClick: type => dispatch(Actions.sortBy(type)),
resizeWaterfall: width => dispatch(Actions.resizeWaterfall(width)),
(dispatch) => ({
resetColumns: () => dispatch(Actions.resetColumns()),
resizeWaterfall: (width) => dispatch(Actions.resizeWaterfall(width)),
sortBy: (type) => dispatch(Actions.sortBy(type)),
toggleColumn: (column) => dispatch(Actions.toggleColumn(column)),
})
)(RequestListHeader);

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

@ -11,7 +11,6 @@ const {
PropTypes,
} = require("devtools/client/shared/vendor/react");
const I = require("devtools/client/shared/vendor/immutable");
const { propertiesEqual } = require("../utils/request-utils");
// Components
@ -55,9 +54,10 @@ const UPDATED_REQ_ITEM_PROPS = [
];
const UPDATED_REQ_PROPS = [
"firstRequestStartedMillis",
"index",
"isSelected",
"firstRequestStartedMillis",
"waterfallWidth",
];
/**
@ -78,11 +78,12 @@ const RequestListItem = createClass({
onFocusedNodeChange: PropTypes.func,
onMouseDown: PropTypes.func.isRequired,
onSecurityIconClick: PropTypes.func.isRequired,
waterfallWidth: PropTypes.number,
},
componentDidMount() {
if (this.props.isSelected) {
this.refs.el.focus();
this.refs.listItem.focus();
}
},
@ -94,7 +95,7 @@ const RequestListItem = createClass({
componentDidUpdate(prevProps) {
if (!prevProps.isSelected && this.props.isSelected) {
this.refs.el.focus();
this.refs.listItem.focus();
if (this.props.onFocusedNodeChange) {
this.props.onFocusedNodeChange();
}
@ -102,7 +103,7 @@ const RequestListItem = createClass({
},
render() {
const {
let {
columns,
item,
index,
@ -112,23 +113,16 @@ const RequestListItem = createClass({
onContextMenu,
onMouseDown,
onCauseBadgeClick,
onSecurityIconClick
onSecurityIconClick,
} = this.props;
let classList = ["request-list-item"];
if (isSelected) {
classList.push("selected");
}
if (fromCache) {
classList.push("fromCache");
}
classList.push(index % 2 ? "odd" : "even");
let classList = ["request-list-item", index % 2 ? "odd" : "even"];
isSelected && classList.push("selected");
fromCache && classList.push("fromCache");
return (
div({
ref: "el",
ref: "listItem",
className: classList.join(" "),
"data-id": item.id,
tabIndex: 0,

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

@ -10,9 +10,9 @@ const {
PropTypes,
} = require("devtools/client/shared/vendor/react");
const Editor = require("devtools/client/sourceeditor/editor");
const { SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE } = require("../constants");
const { div } = DOM;
const SYNTAX_HIGHLIGHT_MAX_SIZE = 51200;
/**
* CodeMirror editor as a React component
@ -33,7 +33,7 @@ const SourceEditor = createClass({
this.editor = new Editor({
lineNumbers: true,
lineWrapping: false,
mode: text.length < SYNTAX_HIGHLIGHT_MAX_SIZE ? mode : null,
mode: text.length < SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE ? mode : null,
readOnly: true,
theme: "mozilla",
value: text,
@ -48,7 +48,8 @@ const SourceEditor = createClass({
componentDidUpdate(prevProps) {
const { mode, text } = this.props;
if (prevProps.mode !== mode && text.length < SYNTAX_HIGHLIGHT_MAX_SIZE) {
if (prevProps.mode !== mode &&
text.length < SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE) {
this.editor.setMode(mode);
}

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

@ -10,7 +10,6 @@ const {
} = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { PluralForm } = require("devtools/shared/plural-form");
const Actions = require("../actions/index");
const {
getDisplayedRequestsSummary,
@ -18,12 +17,11 @@ const {
} = require("../selectors/index");
const {
getFormattedSize,
getFormattedTime
getFormattedTime,
} = require("../utils/format-utils");
const { L10N } = require("../utils/l10n");
// Components
const { div, button, span } = DOM;
const { button, div } = DOM;
function StatusBar({ summary, openStatistics, timingMarkers }) {
let { count, contentSize, transferredSize, millis } = summary;
@ -42,29 +40,32 @@ function StatusBar({ summary, openStatistics, timingMarkers }) {
getFormattedTime(millis));
return (
div({ className: "devtools-toolbar devtools-toolbar-bottom" },
div({ className: "devtools-toolbar devtools-status-bottom" },
button({
className: "devtools-button requests-list-network-summary-button",
onClick: openStatistics,
},
span({ className: "summary-info-icon" }),
div({ className: "summary-info-icon" }),
),
div({ className: "status-bar-label requests-list-network-summary-count" },
countText
),
span({ className: "status-bar-label requests-list-network-summary-count" },
countText),
count !== 0 &&
span({ className: "status-bar-label requests-list-network-summary-transfer" },
transferText),
div({ className: "status-bar-label requests-list-network-summary-transfer" },
transferText
),
count !== 0 &&
span({ className: "status-bar-label requests-list-network-summary-finish" },
finishText),
div({ className: "status-bar-label requests-list-network-summary-finish" },
finishText
),
DOMContentLoaded > -1 &&
span({ className: "status-bar-label dom-content-loaded" },
`DOMContentLoaded: ${getFormattedTime(DOMContentLoaded)}`),
div({ className: "status-bar-label dom-content-loaded" },
`DOMContentLoaded: ${getFormattedTime(DOMContentLoaded)}`
),
load > -1 &&
span({ className: "status-bar-label load" },
`load: ${getFormattedTime(load)}`),
div({ className: "status-bar-label load" },
`load: ${getFormattedTime(load)}`
),
)
);
}

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

@ -93,18 +93,6 @@ const EVENTS = {
UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
// When the request post params are displayed in the UI.
REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
// When the image response thumbnail is displayed in the UI.
RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
"NetMonitor:ResponseImageThumbnailAvailable",
// Fired when charts have been displayed in the PerformanceStatisticsView.
PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
// Fired once the NetMonitorController establishes a connection to the debug
// target.
CONNECTED: "connected",
@ -123,7 +111,6 @@ const HEADERS = [
},
{
name: "file",
boxName: "icon-and-file",
canFilter: false,
},
{
@ -132,7 +119,6 @@ const HEADERS = [
},
{
name: "domain",
boxName: "security-and-domain",
canFilter: true,
},
{
@ -164,13 +150,30 @@ const HEADERS = [
}
];
const REQUESTS_WATERFALL = {
BACKGROUND_TICKS_MULTIPLE: 5, // ms
BACKGROUND_TICKS_SCALES: 3,
BACKGROUND_TICKS_SPACING_MIN: 10, // px
BACKGROUND_TICKS_COLOR_RGB: [128, 136, 144],
// 8-bit value of the alpha component of the tick color
BACKGROUND_TICKS_OPACITY_MIN: 32,
BACKGROUND_TICKS_OPACITY_ADD: 32,
// RGBA colors for the timing markers
DOMCONTENTLOADED_TICKS_COLOR_RGBA: [0, 0, 255, 128],
HEADER_TICKS_MULTIPLE: 5, // ms
HEADER_TICKS_SPACING_MIN: 60, // px
LOAD_TICKS_COLOR_RGBA: [255, 0, 0, 128],
// Reserve extra space for rendering waterfall time label
LABEL_WIDTH: 50, // px
};
const general = {
ACTIVITY_TYPE,
EVENTS,
FILTER_SEARCH_DELAY: 200,
HEADERS,
// 100 KB in bytes
SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE: 102400,
SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE: 51200, // 50 KB in bytes
REQUESTS_WATERFALL,
};
// flatten constants

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

@ -7,10 +7,7 @@
const { TimelineFront } = require("devtools/shared/fronts/timeline");
const { CurlUtils } = require("devtools/client/shared/curl");
const { ACTIVITY_TYPE, EVENTS } = require("./constants");
const {
getRequestById,
getDisplayedRequestById,
} = require("./selectors/index");
const { getDisplayedRequestById } = require("./selectors/index");
const {
fetchHeaders,
formDataURI,
@ -307,7 +304,6 @@ function NetworkEventsHandler() {
this._onRequestPostData = this._onRequestPostData.bind(this);
this._onResponseHeaders = this._onResponseHeaders.bind(this);
this._onResponseCookies = this._onResponseCookies.bind(this);
this._onResponseContent = this._onResponseContent.bind(this);
this._onSecurityInfo = this._onSecurityInfo.bind(this);
this._onEventTimings = this._onEventTimings.bind(this);
}
@ -428,45 +424,11 @@ NetworkEventsHandler.prototype = {
.then(() => window.emit(EVENTS.REQUEST_ADDED, id));
},
async updateRequest(id, data) {
await this.actions.updateRequest(id, data, true);
let {
responseContent,
responseCookies,
responseHeaders,
requestCookies,
requestHeaders,
requestPostData,
} = data;
let request = getRequestById(window.gStore.getState(), id);
if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
let headers = await fetchHeaders(requestHeaders, getLongString);
if (headers) {
await this.actions.updateRequest(
id,
{ requestHeaders: headers },
true,
);
}
}
if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
let headers = await fetchHeaders(responseHeaders, getLongString);
if (headers) {
await this.actions.updateRequest(
id,
{ responseHeaders: headers },
true,
);
}
}
if (request && responseContent && responseContent.content) {
let { mimeType } = request;
let { text, encoding } = responseContent.content;
async fetchImage(mimeType, responseContent) {
let payload = {};
if (mimeType && responseContent && responseContent.content) {
let { encoding, text } = responseContent.content;
let response = await getLongString(text);
let payload = {};
if (mimeType.includes("image/")) {
payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
@ -474,16 +436,36 @@ NetworkEventsHandler.prototype = {
responseContent.content.text = response;
payload.responseContent = responseContent;
}
return payload;
},
await this.actions.updateRequest(id, payload, true);
if (mimeType.includes("image/")) {
window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
async fetchRequestHeaders(requestHeaders) {
let payload = {};
if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
let headers = await fetchHeaders(requestHeaders, getLongString);
if (headers) {
payload.requestHeaders = headers;
}
}
return payload;
},
// Search the POST data upload stream for request headers and add
// them as a separate property, different from the classic headers.
async fetchResponseHeaders(responseHeaders) {
let payload = {};
if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
let headers = await fetchHeaders(responseHeaders, getLongString);
if (headers) {
payload.responseHeaders = headers;
}
}
return payload;
},
// Search the POST data upload stream for request headers and add
// them as a separate property, different from the classic headers.
async fetchPostData(requestPostData) {
let payload = {};
if (requestPostData && requestPostData.postData) {
let { text } = requestPostData.postData;
let postData = await getLongString(text);
@ -491,35 +473,15 @@ NetworkEventsHandler.prototype = {
const headersSize = headers.reduce((acc, { name, value }) => {
return acc + name.length + value.length + 2;
}, 0);
let payload = {};
requestPostData.postData.text = postData;
payload.requestPostData = Object.assign({}, requestPostData);
payload.requestHeadersFromUploadStream = { headers, headersSize };
await this.actions.updateRequest(id, payload, true);
}
// Fetch request and response cookies long value.
// Actor does not provide full sized cookie value when the value is too long
// To display values correctly, we need fetch them in each request.
if (requestCookies) {
let reqCookies = [];
// request store cookies in requestCookies or requestCookies.cookies
let cookies = requestCookies.cookies ?
requestCookies.cookies : requestCookies;
// make sure cookies is iterable
if (typeof cookies[Symbol.iterator] === "function") {
for (let cookie of cookies) {
reqCookies.push(Object.assign({}, cookie, {
value: await getLongString(cookie.value),
}));
}
if (reqCookies.length) {
await this.actions.updateRequest(id, { requestCookies: reqCookies }, true);
}
}
}
return payload;
},
async fetchResponseCookies(responseCookies) {
let payload = {};
if (responseCookies) {
let resCookies = [];
// response store cookies in responseCookies or responseCookies.cookies
@ -533,10 +495,70 @@ NetworkEventsHandler.prototype = {
}));
}
if (resCookies.length) {
await this.actions.updateRequest(id, { responseCookies: resCookies }, true);
payload.responseCookies = resCookies;
}
}
}
return payload;
},
// Fetch request and response cookies long value.
// Actor does not provide full sized cookie value when the value is too long
// To display values correctly, we need fetch them in each request.
async fetchRequestCookies(requestCookies) {
let payload = {};
if (requestCookies) {
let reqCookies = [];
// request store cookies in requestCookies or requestCookies.cookies
let cookies = requestCookies.cookies ?
requestCookies.cookies : requestCookies;
// make sure cookies is iterable
if (typeof cookies[Symbol.iterator] === "function") {
for (let cookie of cookies) {
reqCookies.push(Object.assign({}, cookie, {
value: await getLongString(cookie.value),
}));
}
if (reqCookies.length) {
payload.requestCookies = reqCookies;
}
}
}
return payload;
},
async updateRequest(id, data) {
let {
mimeType,
responseContent,
responseCookies,
responseHeaders,
requestCookies,
requestHeaders,
requestPostData,
} = data;
// fetch request detail contents in parallel
let [
imageObj,
requestHeadersObj,
responseHeadersObj,
postDataObj,
requestCookiesObj,
responseCookiesObj,
] = await Promise.all([
this.fetchImage(mimeType, responseContent),
this.fetchRequestHeaders(requestHeaders),
this.fetchResponseHeaders(responseHeaders),
this.fetchPostData(requestPostData),
this.fetchRequestCookies(requestCookies),
this.fetchResponseCookies(responseCookies),
]);
let payload = Object.assign({}, data,
imageObj, requestHeadersObj, responseHeadersObj,
postDataObj, requestCookiesObj, responseCookiesObj);
await this.actions.updateRequest(id, payload, true);
},
/**
@ -568,9 +590,10 @@ NetworkEventsHandler.prototype = {
case "securityInfo":
this.updateRequest(actor, {
securityState: networkInfo.securityInfo,
}).then(() => {
this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
});
this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
break;
case "responseHeaders":
this.webConsoleClient.getResponseHeaders(actor,
@ -590,25 +613,26 @@ NetworkEventsHandler.prototype = {
status: networkInfo.response.status,
statusText: networkInfo.response.statusText,
headersSize: networkInfo.response.headersSize
}).then(() => {
window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
});
window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
break;
case "responseContent":
this.updateRequest(actor, {
contentSize: networkInfo.response.bodySize,
transferredSize: networkInfo.response.transferredSize,
mimeType: networkInfo.response.content.mimeType
});
this.webConsoleClient.getResponseContent(actor,
this._onResponseContent);
this._onResponseContent.bind(this, {
contentSize: networkInfo.response.bodySize,
transferredSize: networkInfo.response.transferredSize,
mimeType: networkInfo.response.content.mimeType
}));
window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
break;
case "eventTimings":
this.updateRequest(actor, {
totalTime: networkInfo.totalTime
}).then(() => {
this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
});
this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
break;
}
},
@ -700,13 +724,14 @@ NetworkEventsHandler.prototype = {
/**
* Handles additional information received for a "responseContent" packet.
*
* @param object data
* The message received from the server event.
* @param object response
* The message received from the server.
*/
_onResponseContent: function (response) {
this.updateRequest(response.from, {
responseContent: response
}).then(() => {
_onResponseContent: function (data, response) {
let payload = Object.assign({ responseContent: response }, data);
this.updateRequest(response.from, payload).then(() => {
window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
});
},

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

@ -40,15 +40,12 @@ const UI = I.Record({
waterfallWidth: null,
});
// Safe bounds for waterfall width (px)
const REQUESTS_WATERFALL_SAFE_BOUNDS = 90;
function resetColumns(state) {
return state.set("columns", new Columns());
}
function resizeWaterfall(state, action) {
return state.set("waterfallWidth", action.width - REQUESTS_WATERFALL_SAFE_BOUNDS);
return state.set("waterfallWidth", action.width);
}
function openNetworkDetails(state, action) {

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

@ -4,6 +4,7 @@
"use strict";
const { REQUESTS_WATERFALL } = require("../constants");
const { getDisplayedRequests } = require("./requests");
function isNetworkDetailsToggleButtonDisabled(state) {
@ -15,11 +16,7 @@ const EPSILON = 0.001;
function getWaterfallScale(state) {
const { requests, timingMarkers, ui } = state;
if (requests.firstStartedMillis == +Infinity) {
return null;
}
if (ui.waterfallWidth == null) {
if (requests.firstStartedMillis === +Infinity || ui.waterfallWidth === null) {
return null;
}
@ -27,7 +24,8 @@ function getWaterfallScale(state) {
timingMarkers.firstDocumentDOMContentLoadedTimestamp,
timingMarkers.firstDocumentLoadTimestamp);
const longestWidth = lastEventMillis - requests.firstStartedMillis;
return Math.min(Math.max(ui.waterfallWidth / longestWidth, EPSILON), 1);
return Math.min(Math.max(
(ui.waterfallWidth - REQUESTS_WATERFALL.LABEL_WIDTH) / longestWidth, EPSILON), 1);
}
module.exports = {

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

@ -4,23 +4,14 @@
"use strict";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
const REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
// 8-bit value of the alpha component of the tick color
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32;
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32;
// RGBA colors for the timing markers
const REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA = [0, 0, 255, 128];
const REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA = [255, 0, 0, 128];
const { REQUESTS_WATERFALL } = require("./constants");
const HTML_NS = "http://www.w3.org/1999/xhtml";
const STATE_KEYS = [
"scale",
"waterfallWidth",
"firstRequestStartedMillis",
"scale",
"timingMarkers",
"waterfallWidth",
];
/**
@ -62,7 +53,8 @@ WaterfallBackground.prototype = {
}
// Nuke the context.
let canvasWidth = this.canvas.width = state.waterfallWidth;
let canvasWidth = this.canvas.width =
state.waterfallWidth - REQUESTS_WATERFALL.LABEL_WIDTH;
// Awww yeah, 1px, repeats on Y axis.
let canvasHeight = this.canvas.height = 1;
@ -75,14 +67,14 @@ WaterfallBackground.prototype = {
let view32bit = new Uint32Array(buf);
// Build new millisecond tick lines...
let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE;
let timingStep = REQUESTS_WATERFALL.BACKGROUND_TICKS_MULTIPLE;
let optimalTickIntervalFound = false;
let scaledStep;
while (!optimalTickIntervalFound) {
// Ignore any divisions that would end up being too close to each other.
scaledStep = state.scale * timingStep;
if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) {
if (scaledStep < REQUESTS_WATERFALL.BACKGROUND_TICKS_SPACING_MIN) {
timingStep <<= 1;
continue;
}
@ -90,8 +82,8 @@ WaterfallBackground.prototype = {
}
const isRTL = isDocumentRTL(document);
const [r, g, b] = REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
const [r, g, b] = REQUESTS_WATERFALL.BACKGROUND_TICKS_COLOR_RGB;
let alphaComponent = REQUESTS_WATERFALL.BACKGROUND_TICKS_OPACITY_MIN;
function drawPixelAt(offset, color) {
let position = (isRTL ? canvasWidth - offset : offset - 1) | 0;
@ -100,12 +92,12 @@ WaterfallBackground.prototype = {
}
// Insert one pixel for each division on each scale.
for (let i = 1; i <= REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
for (let i = 1; i <= REQUESTS_WATERFALL.BACKGROUND_TICKS_SCALES; i++) {
let increment = scaledStep * Math.pow(2, i);
for (let x = 0; x < canvasWidth; x += increment) {
drawPixelAt(x, [r, g, b, alphaComponent]);
}
alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
alphaComponent += REQUESTS_WATERFALL.BACKGROUND_TICKS_OPACITY_ADD;
}
function drawTimestamp(timestamp, color) {
@ -118,10 +110,10 @@ WaterfallBackground.prototype = {
}
drawTimestamp(state.timingMarkers.firstDocumentDOMContentLoadedTimestamp,
REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA);
REQUESTS_WATERFALL.DOMCONTENTLOADED_TICKS_COLOR_RGBA);
drawTimestamp(state.timingMarkers.firstDocumentLoadTimestamp,
REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA);
REQUESTS_WATERFALL.LOAD_TICKS_COLOR_RGBA);
// Flush the image data and cache the waterfall background.
pixelArray.set(view8bit);

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

@ -9,22 +9,21 @@
add_task(function* () {
let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
const SELECTOR = ".requests-list-icon[src]";
info("Starting test... ");
let { document, gStore, windowRequire } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
let { NetMonitorController } =
windowRequire("devtools/client/netmonitor/src/netmonitor-controller");
let {
ACTIVITY_TYPE,
EVENTS,
} = windowRequire("devtools/client/netmonitor/src/constants");
let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/src/constants");
gStore.dispatch(Actions.batchEnable(false));
let wait = waitForEvents();
let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
yield performRequests();
yield wait;
yield waitUntil(() => !!document.querySelector(SELECTOR));
info("Checking the image thumbnail when all items are shown.");
checkImageThumbnail();
@ -38,22 +37,16 @@ add_task(function* () {
checkImageThumbnail();
info("Reloading the debuggee and performing all requests again...");
wait = waitForEvents();
wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
yield reloadAndPerformRequests();
yield wait;
yield waitUntil(() => !!document.querySelector(SELECTOR));
info("Checking the image thumbnail after a reload.");
checkImageThumbnail();
yield teardown(monitor);
function waitForEvents() {
return promise.all([
waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS),
monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
]);
}
function performRequests() {
return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
@ -66,12 +59,11 @@ add_task(function* () {
}
function checkImageThumbnail() {
is(document.querySelectorAll(".requests-list-icon[data-type=thumbnail]").length, 1,
is(document.querySelectorAll(SELECTOR).length, 1,
"There should be only one image request with a thumbnail displayed.");
is(document.querySelector(".requests-list-icon[data-type=thumbnail]").src,
TEST_IMAGE_DATA_URI,
is(document.querySelector(SELECTOR).src, TEST_IMAGE_DATA_URI,
"The image requests-list-icon thumbnail is displayed correctly.");
is(document.querySelector(".requests-list-icon[data-type=thumbnail]").hidden, false,
is(document.querySelector(SELECTOR).hidden, false,
"The image requests-list-icon thumbnail should not be hidden.");
}
});

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

@ -11,25 +11,22 @@ const IMAGE_TOOLTIP_REQUESTS = 1;
*/
add_task(function* test() {
let { tab, monitor } = yield initNetMonitor(IMAGE_TOOLTIP_URL);
const SELECTOR = ".requests-list-icon[src]";
info("Starting test... ");
let { document, gStore, windowRequire } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
let { NetMonitorController } =
windowRequire("devtools/client/netmonitor/src/netmonitor-controller");
let {
ACTIVITY_TYPE,
EVENTS,
} = windowRequire("devtools/client/netmonitor/src/constants");
let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/src/constants");
let toolboxDoc = monitor.panelWin.parent.document;
gStore.dispatch(Actions.batchEnable(false));
let onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS);
let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
yield performRequests();
yield onEvents;
yield onThumbnail;
yield waitUntil(() => !!document.querySelector(SELECTOR));
info("Checking the image thumbnail after a few requests were made...");
yield showTooltipAndVerify(document.querySelectorAll(".request-list-item")[0]);
@ -41,13 +38,12 @@ add_task(function* test() {
// +1 extra document reload
onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS + 1);
onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
info("Reloading the debuggee and performing all requests again...");
yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
yield performRequests();
yield onEvents;
yield onThumbnail;
yield waitUntil(() => !!document.querySelector(SELECTOR));
info("Checking the image thumbnail after a reload.");
yield showTooltipAndVerify(document.querySelectorAll(".request-list-item")[1]);
@ -71,7 +67,7 @@ add_task(function* test() {
* with the expected content.
*/
function* showTooltipAndVerify(target) {
let anchor = target.querySelector(".requests-list-file");
let anchor = target.querySelector(".requests-list-icon");
yield showTooltipOn(anchor);
info("Tooltip was successfully opened for the image request.");

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

@ -25,7 +25,7 @@ add_task(function* () {
yield performRequests();
for (let subitemNode of Array.from(document.querySelectorAll(
"requests-list-subitem.requests-list-security-and-domain"))) {
"requests-list-column.requests-list-security-and-domain"))) {
let domain = subitemNode.querySelector(".requests-list-domain").textContent;
info("Found a request to " + domain);

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

@ -193,7 +193,7 @@ function test() {
);
});
monitor.panelWin.once(EVENTS.UPDATING_RESPONSE_CONTENT, () => {
monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
let requestItem = getSortedRequests(gStore.getState()).get(0);
is(requestItem.transferredSize, "12",
@ -203,24 +203,6 @@ function test() {
is(requestItem.mimeType, "text/plain; charset=utf-8",
"The mimeType data has an incorrect value.");
verifyRequestItemTarget(
document,
getDisplayedRequests(gStore.getState()),
requestItem,
"GET",
SIMPLE_SJS,
{
type: "plain",
fullMimeType: "text/plain; charset=utf-8",
transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
}
);
});
monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
let requestItem = getSortedRequests(gStore.getState()).get(0);
ok(requestItem.responseContent,
"There should be a responseContent data available.");
// eslint-disable-next-line mozilla/no-cpows-in-tests

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

@ -8,6 +8,13 @@
*/
add_task(function* () {
// Hide file, protocol, remoteip columns to make sure timing division
// can render properly
Services.prefs.setCharPref(
"devtools.netmonitor.hiddenColumns",
"[\"file\",\"protocol\",\"remoteip\"]"
);
let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
info("Starting test... ");

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

@ -86,6 +86,9 @@ Services.prefs.setBoolPref("devtools.debugger.log", false);
// Always reset some prefs to their original values after the test finishes.
const gDefaultFilters = Services.prefs.getCharPref("devtools.netmonitor.filters");
// Reveal all hidden columns for test
Services.prefs.setCharPref("devtools.netmonitor.hiddenColumns", "[]");
registerCleanupFunction(() => {
info("finish() was called, cleaning up...");
@ -374,7 +377,8 @@ function verifyRequestItemTarget(document, requestList, requestItem, method,
let name = getUrlBaseName(url);
let query = getUrlQuery(url);
let hostPort = getUrlHost(url);
let remoteAddress = requestItem.remoteAddress;
let { httpVersion = "", remoteAddress, remotePort } = requestItem;
let remoteIP = remoteAddress ? `${remoteAddress}:${remotePort}` : "unknown";
if (fuzzyUrl) {
ok(requestItem.method.startsWith(method), "The attached method is correct.");
@ -400,6 +404,12 @@ function verifyRequestItemTarget(document, requestList, requestItem, method,
unicodeUrl, "The tooltip file is correct.");
}
is(target.querySelector(".requests-list-protocol").textContent,
httpVersion, "The displayed protocol is correct.");
is(target.querySelector(".requests-list-protocol").getAttribute("title"),
httpVersion, "The tooltip protocol is correct.");
is(target.querySelector(".requests-list-domain").textContent,
hostPort, "The displayed domain is correct.");
@ -407,6 +417,12 @@ function verifyRequestItemTarget(document, requestList, requestItem, method,
is(target.querySelector(".requests-list-domain").getAttribute("title"),
domainTooltip, "The tooltip domain is correct.");
is(target.querySelector(".requests-list-remoteip").textContent,
remoteIP, "The displayed remote IP is correct.");
is(target.querySelector(".requests-list-remoteip").getAttribute("title"),
remoteIP, "The tooltip remote IP is correct.");
if (status !== undefined) {
let value = target.querySelector(".requests-list-status-icon")
.getAttribute("data-code");
@ -421,12 +437,13 @@ function verifyRequestItemTarget(document, requestList, requestItem, method,
is(tooltip, status + " " + statusText, "The tooltip status is correct.");
}
if (cause !== undefined) {
let value = target.querySelector(".requests-list-cause > .subitem-label").textContent;
let value = Array.from(target.querySelector(".requests-list-cause").childNodes)
.filter((node) => node.nodeType === Node.TEXT_NODE)[0].textContent;
let tooltip = target.querySelector(".requests-list-cause").getAttribute("title");
info("Displayed cause: " + value);
info("Tooltip cause: " + tooltip);
is(value, cause.type, "The displayed cause is correct.");
is(tooltip, cause.loadingDocumentUri, "The tooltip cause is correct.");
is(tooltip, cause.type, "The tooltip cause is correct.");
}
if (type !== undefined) {
let value = target.querySelector(".requests-list-type").textContent;

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

@ -29,7 +29,7 @@ const PriorityLevels = {
/**
* This component represents Notification Box - HTML alternative for
* <xul:notifictionbox> binding.
* <xul:notificationbox> binding.
*
* See also MDN for more info about <xul:notificationbox>:
* https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox

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

@ -800,6 +800,17 @@ a.learn-more-link.webconsole-learn-more-link {
display: none;
}
/*
* Open DOMNode in inspector button. Style need to be reset in the new
* console since its style is already defined in reps.css .
*/
.webconsole-output-wrapper .open-inspector {
background: unset;
padding-left: unset;
margin-left: unset;
cursor: unset;
}
/* console.table() */
.new-consoletable {
width: 100%;

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

@ -61,9 +61,15 @@ function GripMessageBody(props) {
let onDOMNodeMouseOver;
let onDOMNodeMouseOut;
let onInspectIconClick;
if (serviceContainer) {
onDOMNodeMouseOver = (object) => serviceContainer.highlightDomElement(object);
onDOMNodeMouseOver = serviceContainer.highlightDomElement
? (object) => serviceContainer.highlightDomElement(object)
: null;
onDOMNodeMouseOut = serviceContainer.unHighlightDomElement;
onInspectIconClick = serviceContainer.openNodeInInspector
? (object) => serviceContainer.openNodeInInspector(object)
: null;
}
return (
@ -81,6 +87,7 @@ function GripMessageBody(props) {
objectLink: VariablesViewLink,
onDOMNodeMouseOver,
onDOMNodeMouseOut,
onInspectIconClick,
defaultRep: Grip,
mode: props.mode,
})

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

@ -57,9 +57,9 @@ function NetworkEventMessage({
statusInfo = `[${httpVersion} ${status} ${statusText} ${totalTime}ms]`;
}
function openNetworkMonitor() {
serviceContainer.openNetworkPanel(actor);
}
const openNetworkMonitor = serviceContainer.openNetworkPanel
? () => serviceContainer.openNetworkPanel(actor)
: null;
const method = dom.span({className: "method" }, request.method);
const xhr = isXHR

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

@ -49,9 +49,9 @@ const Message = createClass({
timeStamp: PropTypes.number,
serviceContainer: PropTypes.shape({
emitNewMessage: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
onViewSourceInScratchpad: PropTypes.func.isRequired,
onViewSourceInStyleEditor: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func,
onViewSourceInScratchpad: PropTypes.func,
onViewSourceInStyleEditor: PropTypes.func,
openContextMenu: PropTypes.func.isRequired,
openLink: PropTypes.func.isRequired,
sourceMapService: PropTypes.any,

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

@ -38,16 +38,42 @@ NewConsoleOutputWrapper.prototype = {
this.jsterm.hud[id] = node;
};
let childComponent = ConsoleOutput({
serviceContainer: {
attachRefToHud,
emitNewMessage: (node, messageId) => {
this.jsterm.hud.emit("new-messages", new Set([{
node,
messageId,
}]));
},
hudProxyClient: this.jsterm.hud.proxy.client,
const serviceContainer = {
attachRefToHud,
emitNewMessage: (node, messageId) => {
this.jsterm.hud.emit("new-messages", new Set([{
node,
messageId,
}]));
},
hudProxyClient: this.jsterm.hud.proxy.client,
openContextMenu: (e, message) => {
let { screenX, screenY, target } = e;
let messageEl = target.closest(".message");
let clipboardText = messageEl ? messageEl.textContent : null;
// Retrieve closes actor id from the DOM.
let actorEl = target.closest("[data-link-actor-id]");
let actor = actorEl ? actorEl.dataset.linkActorId : null;
let menu = createContextMenu(this.jsterm, this.parentNode,
{ actor, clipboardText, message });
// Emit the "menu-open" event for testing.
menu.once("open", () => this.emit("menu-open"));
menu.popup(screenX, screenY, this.toolbox);
return menu;
},
openLink: url => this.jsterm.hud.owner.openLink(url),
createElement: nodename => {
return this.document.createElementNS("http://www.w3.org/1999/xhtml", nodename);
},
};
if (this.toolbox) {
Object.assign(serviceContainer, {
onViewSourceInDebugger: frame => {
this.toolbox.viewSourceInDebugger(frame.url, frame.line).then(() =>
this.jsterm.hud.emit("source-in-debugger-opened")
@ -61,25 +87,6 @@ NewConsoleOutputWrapper.prototype = {
frame.url,
frame.line
),
openContextMenu: (e, message) => {
let { screenX, screenY, target } = e;
let messageEl = target.closest(".message");
let clipboardText = messageEl ? messageEl.textContent : null;
// Retrieve closes actor id from the DOM.
let actorEl = target.closest("[data-link-actor-id]");
let actor = actorEl ? actorEl.dataset.linkActorId : null;
let menu = createContextMenu(this.jsterm, this.parentNode,
{ actor, clipboardText, message });
// Emit the "menu-open" event for testing.
menu.once("open", () => this.emit("menu-open"));
menu.popup(screenX, screenY, this.toolbox);
return menu;
},
openNetworkPanel: (requestId) => {
return this.toolbox.selectTool("netmonitor").then(panel => {
return panel.panelWin.NetMonitorController.inspectRequest(requestId);
@ -87,22 +94,34 @@ NewConsoleOutputWrapper.prototype = {
},
sourceMapService:
this.toolbox ? this.toolbox._deprecatedServerSourceMapService : null,
openLink: url => this.jsterm.hud.owner.openLink(url),
createElement: nodename => {
return this.document.createElementNS("http://www.w3.org/1999/xhtml", nodename);
},
highlightDomElement: (grip, options = {}) => {
return this.toolbox && this.toolbox.highlighterUtils
return this.toolbox.highlighterUtils
? this.toolbox.highlighterUtils.highlightDomValueGrip(grip, options)
: null;
},
unHighlightDomElement: (forceHide = false) => {
return this.toolbox && this.toolbox.highlighterUtils
return this.toolbox.highlighterUtils
? this.toolbox.highlighterUtils.unhighlight(forceHide)
: null;
},
}
});
openNodeInInspector: async (grip) => {
let onSelectInspector = this.toolbox.selectTool("inspector");
let onGripNodeToFront = this.toolbox.highlighterUtils.gripToNodeFront(grip);
let [
front,
inspector
] = await Promise.all([onGripNodeToFront, onSelectInspector]);
let onInspectorUpdated = inspector.once("inspector-updated");
let onNodeFrontSet = this.toolbox.selection.setNodeFront(front, "console");
return Promise.all([onNodeFrontSet, onInspectorUpdated]);
}
});
}
let childComponent = ConsoleOutput({serviceContainer});
let filterBar = FilterBar({
serviceContainer: {
attachRefToHud

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

@ -38,6 +38,7 @@ skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32
[browser_webconsole_location_scratchpad_link.js]
[browser_webconsole_location_styleeditor_link.js]
[browser_webconsole_nodes_highlight.js]
[browser_webconsole_nodes_select.js]
[browser_webconsole_observer_notifications.js]
[browser_webconsole_stacktrace_location_debugger_link.js]
[browser_webconsole_stacktrace_location_scratchpad_link.js]

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

@ -0,0 +1,58 @@
/* 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";
// Check clicking on open-in-inspector icon does select the node in the inspector.
const HTML = `
<!DOCTYPE html>
<html>
<body>
<h1>Select node in inspector test</h1>
</body>
<script>
function logNode(selector) {
console.log(document.querySelector(selector));
}
</script>
</html>
`;
const TEST_URI = "data:text/html;charset=utf-8," + encodeURI(HTML);
add_task(async function () {
const hud = await openNewTabAndConsole(TEST_URI);
const toolbox = gDevTools.getToolbox(hud.target);
// Loading the inspector panel at first, to make it possible to listen for
// new node selections
await toolbox.loadTool("inspector");
const inspector = toolbox.getPanel("inspector");
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
content.wrappedJSObject.logNode("h1");
});
let msg = await waitFor(() => findMessage(hud, "<h1>"));
let node = msg.querySelector(".objectBox-node");
ok(node !== null, "Node was logged as expected");
let openInInspectorIcon = node.querySelector(".open-inspector");
ok(openInInspectorIcon !== null, "The is an open in inspector icon");
info("Clicking on the inspector icon and waiting for the " +
"inspector to be selected");
let onInspectorSelected = toolbox.once("inspector-selected");
let onInspectorUpdated = inspector.once("inspector-updated");
let onNewNode = toolbox.selection.once("new-node-front");
openInInspectorIcon.click();
await onInspectorSelected;
await onInspectorUpdated;
let nodeFront = await onNewNode;
ok(true, "Inspector selected and new node got selected");
is(nodeFront.displayName, "h1", "The expected node was selected");
});

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

@ -101,11 +101,9 @@ function waitForMessages({ hud, messages }) {
* @return object
* A promise that is resolved with the result of the condition.
*/
function* waitFor(condition, message = "waitFor", interval = 10, maxTries = 500) {
return new Promise(resolve => {
BrowserTestUtils.waitForCondition(condition, message, interval, maxTries)
.then(() => resolve(condition()));
});
async function waitFor(condition, message = "waitFor", interval = 10, maxTries = 500) {
await BrowserTestUtils.waitForCondition(condition, message, interval, maxTries);
return condition();
}
/**

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

@ -30,3 +30,9 @@
* [Actor Best Practices](backend/actor-best-practices.md)
* [Files and directories](files/README.md)
* [Adding New Files](files/adding-files.md)
* [Automated tests](tests/README.md)
* Running tests
* [`xpcshell`](tests/xpcshell.md)
* [Chrome mochitests](tests/mochitest-chrome.md)
* [DevTools mochitests](tests/mochitest-devtools.md)
* [Writing tests](tests/writing-tests.md)

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

@ -0,0 +1,23 @@
# Automated tests
When working on a patch for DevTools, there's almost never a reason not to add a new test. If you are fixing a bug, you probably should write a new test to prevent this bug from occurring again. If you're implementing a new feature, you should write new tests to cover the aspects of this new feature.
Ask yourself:
* Are there enough tests for my patch?
* Are they the right types of tests?
We use three suites of tests:
* [`xpcshell`](xpcshell.md): Unit-test style of tests. No browser window, only a JavaScript shell. Mostly testing APIs directly.
* [Chrome mochitests](mochitest-chrome.md): Unit-test style of tests, but with a browser window. Mostly testing APIs that interact with the DOM.
* [DevTools mochitests](mochitest-devtools.md): Integration style of tests. Fires up a whole browser window with every test and you can test clicking on buttons, etc.
More information about the different types of tests can be found on the [automated testing page](https://developer.mozilla.org/en-US/docs/Mozilla/QA/Automated_testing) at MDN.
To run all DevTools tests, regardless of suite type:
```bash
./mach test devtools/*
```
Have a look at the child pages for more specific commands for running only a single suite or single test in a suite.

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

@ -0,0 +1,14 @@
# Automated tests: chrome mochitests
To run the whole suite of chrome mochitests:
```bash
./mach mochitest -f chrome --tag devtools
```
To run a specific chrome mochitest:
```bash
./mach mochitest devtools/path/to/the/test_you_want_to_run.html
```

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

@ -0,0 +1,25 @@
# Automated tests: DevTools mochitests
To run the whole suite of browser mochitests for DevTools (sit back and relax):
```bash
./mach mochitest --subsuite devtools --tag devtools
```
To run a specific tool's suite of browser mochitests:
```bash
./mach mochitest devtools/client/<tool>
```
For example, run all of the debugger browser mochitests:
```bash
./mach mochitest devtools/client/debugger
```
To run a specific DevTools mochitest:
```bash
./mach mochitest devtools/client/path/to/the/test_you_want_to_run.js
```
Note that the mochitests *must* have focus while running.

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

@ -0,0 +1,255 @@
# Automated tests: writing tests
<!--TODO this file might benefit from being split in other various files. For now it's just taken from the wiki with some edits-->
## Adding a new browser chrome test
It's almost always a better idea to create a new test file rather than to add new test cases to an existing one.
This prevents test files from growing up to the point where they timeout for running too long. Test systems may be under lots of stress at time and run a lot slower than your regular local environment.
It also helps with making tests more maintainable: with many small files, it's easier to track a problem rather than in one huge file.
### Creating the new file
The first thing you need to do is create a file. This file should go next to the code it's testing, in the `tests` directory. For example, an inspector test would go into `devtools/inspector/test/`.
### Naming the new file
Naming your file is pretty important to help other people get a feeling of what it is supposed to test.
Having said that, the name shouldn't be too long either.
A good naming convention is `browser_<panel>_<short-description>[_N].js`
where:
* `<panel>` is one of `debugger`, `markupview`, `inspector`, `ruleview`, etc.
* `<short-description>` should be about 3 to 4 words, separated by hyphens (-)
* and optionally add a number at the end if you have several files testing the same thing
For example: `browser_ruleview_completion-existing-property_01.js`
Note that not all existing tests are consistently named. So the rule we try to follow is to **be consistent with how other tests in the same test folder are named**.
### Basic structure of a test
```javascript
/* 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";
// A detailed description of what the test is supposed to test
const TEST_URL = TEST_URL_ROOT + "doc_some_test_page.html";
add_task(function*() {
yield addTab(TEST_URL_ROOT);
let {toolbox, inspector, view} = yield openRuleView();
yield selectNode("#testNode", inspector);
yield checkSomethingFirst(view);
yield checkSomethingElse(view);
});
function* checkSomethingFirst(view) {
/* ... do something ... this function can yield */
}
function* checkSomethingElse(view) {
/* ... do something ... this function can yield */
}
```
### Referencing the new file
For your test to be run, it needs to be referenced in the `browser.ini` file that you'll find in the same directory. For example: `browser/devtools/debugger/test/browser.ini`
Add a line with your file name between square brackets, and make sure that the list of files **is always sorted by alphabetical order** (some lists can be really long, so the alphabetical order helps in finding and reasoning about things).
For example, if you were to add the test from the previous section, you'd add this to `browser.ini`:
```ini
[browser_ruleview_completion-existing-property_01.js]
```
### Adding support files
Sometimes your test may need to open an HTML file in a tab, and it may also need to load CSS or JavaScript. For this to work, you'll need to...
1. place these files in the same directory, and also
2. reference them in the `browser.ini` file.
There's a naming convention for support files: `doc_<support-some-test>.html`
But again, often names do not follow this convention, so try to follow the style of the other support files currently in the same test directory.
To reference your new support file, add its filename in the `support-files` section of `browser.ini`, also making sure this section is in alphabetical order.
Support files can be accessed via a local server that is started while tests are running. This server is accessible at [http://example.com/browser/](http://example.com/browser/). See the `head.js section` below for more information.
## Leveraging helpers in `head.js`
`head.js` is a special support file that is loaded in the scope the test runs in, before the test starts. It contains global helpers that are useful for most tests. Read through the head.js file in your test directory to see what functions are there and therefore avoid duplicating code.
Each panel in DevTools has its own test directory with its own `head.js`, so you'll find different things in each panel's `head.js` file.
For example, the head.js files in the `markupview` and `styleinspector` test folders contain these useful functions and constants:
* Base URLs for support files: `TEST_URL_ROOT`. This avoids having to duplicate the http://example.com/browser/browser/devtools/styleinspector/ URL fragment in all tests,
* `waitForExplicitFinish()` is called in `head.js` once and for all<!--TODO: what does this even mean?-->. All tests are asynchronous, so there's no need to call it again in each and every test,
* `auto-cleanup`: the toolbox is closed automatically and all tabs are closed,
* `tab addTab(url)`
* `{toolbox, inspector} openInspector()`
* `{toolbox, inspector, view} openRuleView()`
* `selectNode(selectorOrNode, inspector)`
* `node getNode(selectorOrNode)`
* ...
## Shared head.js file
A [shared-head.js](https://dxr.mozilla.org/mozilla-central/source/devtools/client/framework/test/shared-head.js) file has been introduced to avoid duplicating code in various `head.js` files.
It's important to know whether or not the `shared.js` in your test directory already imports `shared-head.js` (look for a <code>Services.scriptloader.loadSubScript</code> call), as common helpers in `shared-head.js` might be useful for your test.
If you're planning to work on a lot of new tests, it might be worth the time actually importing `shared-head.js` in your `head.js` if it isn't here already.
## E10S (Electrolysis)
E10S is the codename for Firefox multi-process, and what that means for us is that the process in which the test runs isn't the same as the one in which the test content page runs.
You can learn more about E10S [from this blog post](https://timtaubert.de/blog/2011/08/firefox-electrolysis-101/) and [the Electrolysis wiki page](https://wiki.mozilla.org/Electrolysis).
One of the direct consequences of E10S on tests is that you cannot retrieve and manipulate objects from the content page as you'd do without E10S.
Well this isn't entirely true, because with [cross-process object wrappers](https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Cross_Process_Object_Wrappers CPOWs) you can somehow access the page, get to DOM nodes, and read their attributes for instance, but a lot of other things you'd expect to work without E10S won't work exactly the same or at all.
Using CPOWs is discouraged; they are only temporarily allowed in mochitests, and their use is forbidden in browser code.
So when creating a new test, if this test needs to access the content page in any way, you can use [the message manager](https://developer.mozilla.org/en-US/docs/The_message_manager) to communicate with a script loaded in the content process to do things for you instead of accessing objects in the page directly.
You can use the helper `ContentTask.spawn()` for this. See [this list of DevTools tests that use that helper for examples](https://dxr.mozilla.org/mozilla-central/search?q=ContentTask.spawn%28+path%3Adevtools%2Fclient&redirect=false&case=false).
Note that a lot of tests only need to access the DevTools UI anyway, and don't need to interact with the content process at all. Since the UI lives in the same process as the test, you won't need to use the message manager to access it.
## Asynchronous tests
Most browser chrome DevTools tests are asynchronous. One of the reasons why they are asynchronous is that the code needs to register event handlers for various user interactions in the tools and then simulate these interactions. Another reason is that most DevTools operations are done asynchronously via the debugger protocol.
Here are a few things to keep in mind with regards to asynchronous testing:
* `head.js` already calls `waitForExplicitFinish()` so there's no need for your new test to do it too.
* Using `add_task` with a generator function means that you can yield calls to functions that return promises. It also means your main test function can be written to almost look like synchronous code, by adding `yield` before calls to asynchronous functions. For example:
```javascript
for (let test of testData) {
yield testCompletion(test, editor, view);
}
```
Each call to `testCompletion` is asynchronous, but the code doesn't need to rely on nested callbacks and maintain an index, a standard for loop can be used.
<!--TODO: I think the following paragraph might be slightly outdated-->
* Define your test functions as generators that yield, no need for them to be tasks since they are called from one already. In some cases you'll need to return promises anyway (if you're adding a new helper function to head.js for example). If this is the case, it sometimes is best to define your function like so:
```javascript
let myHelperFunction = Task.async(function*() {
...
});
```
## Writing clean, maintainable test code
Test code is as important as feature code itself, it helps avoiding regressions of course, but it also helps understanding complex parts of the code that would be otherwise hard to grasp.
Since we find ourselves working with test code a large portion of our time, we should spend the time and energy it takes to make this time enjoyable.
### Logs and comments
Reading test output logs isn't exactly fun and it takes time but is needed at times. Make sure your test generates enough logs by using:
```
info("doing something now")
```
it helps a lot knowing around which lines the test fails, if it fails.
One good rule of thumb is if you're about to add a JS line comment in your test to explain what the code below is about to test, write the same comment in an `info()` instead.
Also add a description at the top of the file to help understand what this test is about. The file name is often not long enough to convey everything you need to know about the test. Understanding a test often teaches you about the feature itself.
Not really a comment, but don't forget to "use strict";
### Callbacks and promises
Avoid multiple nested callbacks or chained promises. They make it hard to read the code.
Thanks to `add_task`, it's easy to write asynchronous code that looks like flat, synchronous, code.
### Clean up after yourself
Do not expose global variables in your test file, they may end up causing bugs that are hard to track. Most functions in `head.js` return useful instances of the DevTools panels, and you can pass these as arguments to your sub functions, no need to store them in the global scope.
This avoids having to remember nullifying them at the end.
If your test needs to toggle user preferences, make sure you reset these preferences when the test ends. Do not reset them at the end of the test function though because if your test fails, the preferences will never be reset. Use the `registerCleanupFunction` helper instead.
It may be a good idea to do the reset in `head.js`.
### Write small, maintainable code
Split your main test function into smaller test functions with self explanatory names.
Make sure your test files are small. If you are working on a new feature, you can create a new test each time you add a new functionality, a new button to the UI for instance. This helps having small, incremental tests and can also help writing test while coding.
If your test is just a sequence of functions being called to do the same thing over and over again, it may be better to describe the test steps in an array instead and just have one function that runs each item of the array. See the following example
```javascript
const TESTS = [
{desc: "add a class", cssSelector: "#id1", makeChanges: function*() {...}},
{desc: "change href", cssSelector: "a.the-link", makeChanges: function*() {...}},
...
];
add_task(function*() {
yield addTab("...");
let {toolbox, inspector} = yield openInspector();
for (let step of TESTS) {
info("Testing step: " + step.desc);
yield selectNode(step.cssSelector, inspector);
yield step.makeChanges();
}
});
```
As shown in this code example, you can add as many test cases as you want in the TESTS array and the actual test code will remain very short, and easy to understand and maintain (note that when looping through test arrays, it's always a good idea to add a "desc" property that will be used in an info() log output).
### Avoid exceptions
Even when they're not failing the test, exceptions are bad because they pollute the logs and make them harder to read.
They're also bad because when your test is run as part of a test suite and if an other, unrelated, test fails then the exceptions may give wrong information to the person fixing the unrelated test.
After your test has run locally, just make sure it doesn't output exceptions by scrolling through the logs.
Often, non-blocking exceptions may be caused by hanging protocol requests that haven't been responded to yet when the tools get closed at the end of the test. Make sure you register to the right events and give time to the tools to update themselves before moving on.
### Avoid test timeouts
<!--TODO: this recommendation is conflicting with the above recommendation. What? -->
When tests fail, it's far better to have them fail and end immediately with an exception that will help fix it rather than have them hang until they hit the timeout and get killed.
## Adding new helpers
In some cases, you may want to extract some common code from your test to use it another another test.
* If this is very common code that all tests could use, then add it to `devtools/client/framework/test/shared-head.js`.
* If this is common code specific to a given tool, then add it to the corresponding `head.js` file.
* If it isn't common enough to live in `head.js`, then it may be a good idea to create a helper file to avoid duplication anyway. Here's how to create a helper file:
* Create a new file in your test director. The naming convention should be `helper_<description_of_the_helper>.js`
* Add it to the browser.ini support-files section, making sure it is sorted alphabetically
* Load the helper file in the tests
* `browser/devtools/markupview/test/head.js` has a handy `loadHelperScript(fileName)` function that you can use.
* The file will be loaded in the test global scope, so any global function or variables it defines will be available (just like `head.js`).
* Use the special ESLint comment `/* import-globals-from helper_file.js */` to prevent ESLint errors for undefined variables.
In all cases, new helper functions should be properly commented with an jsdoc comment block.

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

@ -0,0 +1,14 @@
# Automated tests: `xpcshell` tests
To run all of the xpcshell tests:
```bash
./mach xpcshell-test --tag devtools
```
To run a specific xpcshell test:
```bash
./mach xpcshell-test devtools/path/to/the/test_you_want_to_run.js
```

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

@ -47,7 +47,7 @@ function test_pause_frame() {
let location = wasmFrame.where;
do_check_eq(location.line > 0, true);
do_check_eq(location.column > 0, true);
do_check_eq(location.source.url.endsWith(" > wasm"), true);
do_check_eq(/^wasm:(?:[^:]*:)*?[0-9a-f]{16}$/.test(location.source.url), true);
finishClient(gClient);
});

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

@ -1,18 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we can require acorn.
*/
function run_test() {
const acorn = require("acorn/acorn");
const acorn_loose = require("acorn/acorn_loose");
const walk = require("acorn/util/walk");
do_check_true(isObject(acorn));
do_check_true(isObject(acorn_loose));
do_check_true(isObject(walk));
do_check_eq(typeof acorn.parse, "function");
do_check_eq(typeof acorn_loose.parse_dammit, "function");
do_check_eq(typeof walk.simple, "function");
}
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we can require acorn.
*/
function run_test() {
const acorn = require("acorn/acorn");
const acorn_loose = require("acorn/acorn_loose");
const walk = require("acorn/util/walk");
do_check_true(isObject(acorn));
do_check_true(isObject(acorn_loose));
do_check_true(isObject(walk));
do_check_eq(typeof acorn.parse, "function");
do_check_eq(typeof acorn_loose.parse_dammit, "function");
do_check_eq(typeof walk.simple, "function");
}

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

@ -1,62 +1,62 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that acorn's lenient parser gives something usable.
*/
const acorn_loose = require("acorn/acorn_loose");
function run_test() {
let actualAST = acorn_loose.parse_dammit("let x = 10", {});
do_print("Actual AST:");
do_print(JSON.stringify(actualAST, null, 2));
do_print("Expected AST:");
do_print(JSON.stringify(expectedAST, null, 2));
checkEquivalentASTs(expectedAST, actualAST);
}
const expectedAST = {
"type": "Program",
"start": 0,
"end": 10,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 3,
"expression": {
"type": "Identifier",
"start": 0,
"end": 3,
"name": "let"
}
},
{
"type": "ExpressionStatement",
"start": 4,
"end": 10,
"expression": {
"type": "AssignmentExpression",
"start": 4,
"end": 10,
"operator": "=",
"left": {
"type": "Identifier",
"start": 4,
"end": 5,
"name": "x"
},
"right": {
"type": "Literal",
"start": 8,
"end": 10,
"value": 10,
"raw": "10"
}
}
}
]
};
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that acorn's lenient parser gives something usable.
*/
const acorn_loose = require("acorn/acorn_loose");
function run_test() {
let actualAST = acorn_loose.parse_dammit("let x = 10", {});
do_print("Actual AST:");
do_print(JSON.stringify(actualAST, null, 2));
do_print("Expected AST:");
do_print(JSON.stringify(expectedAST, null, 2));
checkEquivalentASTs(expectedAST, actualAST);
}
const expectedAST = {
"type": "Program",
"start": 0,
"end": 10,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 3,
"expression": {
"type": "Identifier",
"start": 0,
"end": 3,
"name": "let"
}
},
{
"type": "ExpressionStatement",
"start": 4,
"end": 10,
"expression": {
"type": "AssignmentExpression",
"start": 4,
"end": 10,
"operator": "=",
"left": {
"type": "Identifier",
"start": 4,
"end": 5,
"name": "x"
},
"right": {
"type": "Literal",
"start": 8,
"end": 10,
"value": 10,
"raw": "10"
}
}
}
]
};

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

@ -1,37 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that Reflect and acorn create the same AST for ES5.
*/
const acorn = require("acorn/acorn");
const { Reflect } = require("resource://gre/modules/reflect.jsm");
const testCode = "" + function main () {
function makeAcc(n) {
return function () {
return ++n;
};
}
var acc = makeAcc(10);
for (var i = 0; i < 10; i++) {
acc();
}
console.log(acc());
};
function run_test() {
const reflectAST = Reflect.parse(testCode);
const acornAST = acorn.parse(testCode);
do_print("Reflect AST:");
do_print(JSON.stringify(reflectAST, null, 2));
do_print("acorn AST:");
do_print(JSON.stringify(acornAST, null, 2));
checkEquivalentASTs(reflectAST, acornAST);
}
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that Reflect and acorn create the same AST for ES5.
*/
const acorn = require("acorn/acorn");
const { Reflect } = require("resource://gre/modules/reflect.jsm");
const testCode = "" + function main () {
function makeAcc(n) {
return function () {
return ++n;
};
}
var acc = makeAcc(10);
for (var i = 0; i < 10; i++) {
acc();
}
console.log(acc());
};
function run_test() {
const reflectAST = Reflect.parse(testCode);
const acornAST = acorn.parse(testCode);
do_print("Reflect AST:");
do_print(JSON.stringify(reflectAST, null, 2));
do_print("acorn AST:");
do_print(JSON.stringify(acornAST, null, 2));
checkEquivalentASTs(reflectAST, acornAST);
}

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

@ -145,6 +145,7 @@ DOM4_MSG_DEF(AbortError, "Error retrieving push subscription.", NS_ERROR_DOM_PUS
DOM4_MSG_DEF(NetworkError, "Push service unreachable.", NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE)
DOM4_MSG_DEF(InvalidAccessError, "Invalid raw ECDSA P-256 public key.", NS_ERROR_DOM_PUSH_INVALID_KEY_ERR)
DOM4_MSG_DEF(InvalidStateError, "A subscription with a different application server key already exists.", NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR)
DOM4_MSG_DEF(InvalidStateError, "GCM service disabled.", NS_ERROR_DOM_PUSH_GCM_DISABLED)
/* Media errors */
DOM4_MSG_DEF(AbortError, "The fetching process for the media resource was aborted by the user agent at the user's request.", NS_ERROR_DOM_MEDIA_ABORT_ERR)

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

@ -10,9 +10,13 @@
#include "nsDOMTokenList.h"
#include "nsAttrValue.h"
#include "nsAttrValueInlines.h"
#include "nsDataHashtable.h"
#include "nsError.h"
#include "nsHashKeys.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/DOMTokenListBinding.h"
#include "mozilla/BloomFilter.h"
#include "mozilla/ErrorResult.h"
using namespace mozilla;
@ -50,6 +54,45 @@ nsDOMTokenList::GetParsedAttr()
return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue;
}
void
nsDOMTokenList::RemoveDuplicates(const nsAttrValue* aAttr)
{
if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) {
return;
}
BloomFilter<8, nsIAtom> filter;
nsAttrValue::AtomArray* array = aAttr->GetAtomArrayValue();
for (uint32_t i = 0; i < array->Length(); i++) {
nsIAtom* atom = array->ElementAt(i);
if (filter.mightContain(atom)) {
// Start again, with a hashtable
RemoveDuplicatesInternal(array, i);
return;
} else {
filter.add(atom);
}
}
}
void
nsDOMTokenList::RemoveDuplicatesInternal(nsAttrValue::AtomArray* aArray,
uint32_t aStart)
{
nsDataHashtable<nsPtrHashKey<nsIAtom>, bool> tokens;
for (uint32_t i = 0; i < aArray->Length(); i++) {
nsIAtom* atom = aArray->ElementAt(i);
// No need to check the hashtable below aStart
if (i >= aStart && tokens.Get(atom)) {
aArray->RemoveElementAt(i);
i--;
} else {
tokens.Put(atom, true);
}
}
}
uint32_t
nsDOMTokenList::Length()
{
@ -58,6 +101,7 @@ nsDOMTokenList::Length()
return 0;
}
RemoveDuplicates(attr);
return attr->GetAtomCount();
}
@ -66,6 +110,13 @@ nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aResult)
{
const nsAttrValue* attr = GetParsedAttr();
if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) {
aFound = false;
return;
}
RemoveDuplicates(attr);
if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) {
aFound = true;
attr->AtomAt(aIndex)->ToString(aResult);
@ -135,10 +186,15 @@ nsDOMTokenList::AddInternal(const nsAttrValue* aAttr,
nsAutoString resultStr;
if (aAttr) {
aAttr->ToString(resultStr);
RemoveDuplicates(aAttr);
for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
if (i != 0) {
resultStr.AppendLiteral(" ");
}
resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
}
}
bool oneWasAdded = false;
AutoTArray<nsString, 10> addedClasses;
for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
@ -149,16 +205,11 @@ nsDOMTokenList::AddInternal(const nsAttrValue* aAttr,
continue;
}
if (oneWasAdded ||
(!resultStr.IsEmpty() &&
!nsContentUtils::IsHTMLWhitespace(resultStr.Last()))) {
if (!resultStr.IsEmpty()) {
resultStr.Append(' ');
resultStr.Append(aToken);
} else {
resultStr.Append(aToken);
}
resultStr.Append(aToken);
oneWasAdded = true;
addedClasses.AppendElement(aToken);
}
@ -191,23 +242,20 @@ nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr,
{
MOZ_ASSERT(aAttr, "Need an attribute");
nsAutoString input;
aAttr->ToString(input);
RemoveDuplicates(aAttr);
WhitespaceTokenizer tokenizer(input);
nsAutoString output;
while (tokenizer.hasMoreTokens()) {
auto& currentToken = tokenizer.nextToken();
if (!aTokens.Contains(currentToken)) {
if (!output.IsEmpty()) {
output.Append(char16_t(' '));
}
output.Append(currentToken);
nsAutoString resultStr;
for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
if (aTokens.Contains(nsDependentAtomString(aAttr->AtomAt(i)))) {
continue;
}
if (!resultStr.IsEmpty()) {
resultStr.AppendLiteral(" ");
}
resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
}
mElement->SetAttr(kNameSpaceID_None, mAttrAtom, output, true);
mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
}
void
@ -303,33 +351,32 @@ nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr,
const nsAString& aToken,
const nsAString& aNewToken)
{
nsAutoString attribute;
aAttr->ToString(attribute);
nsAutoString result;
WhitespaceTokenizer tokenizer(attribute);
RemoveDuplicates(aAttr);
bool sawIt = false;
while (tokenizer.hasMoreTokens()) {
auto currentToken = tokenizer.nextToken();
if (currentToken.Equals(aToken) || currentToken.Equals(aNewToken)) {
if (!sawIt) {
sawIt = true;
if (!result.IsEmpty()) {
result.Append(char16_t(' '));
}
result.Append(aNewToken);
nsAutoString resultStr;
for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
if (aAttr->AtomAt(i)->Equals(aToken) ||
aAttr->AtomAt(i)->Equals(aNewToken)) {
if (sawIt) {
// We keep only the first
continue;
}
} else {
if (!result.IsEmpty()) {
result.Append(char16_t(' '));
sawIt = true;
if (!resultStr.IsEmpty()) {
resultStr.AppendLiteral(" ");
}
result.Append(currentToken);
resultStr.Append(aNewToken);
continue;
}
if (!resultStr.IsEmpty()) {
resultStr.AppendLiteral(" ");
}
resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
}
if (sawIt) {
mElement->SetAttr(kNameSpaceID_None, mAttrAtom, result, true);
mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
}
}

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

@ -52,6 +52,7 @@ public:
return mElement;
}
void RemoveDuplicates(const nsAttrValue* aAttr);
uint32_t Length();
void Item(uint32_t aIndex, nsAString& aResult)
{
@ -87,6 +88,8 @@ protected:
nsresult CheckToken(const nsAString& aStr);
nsresult CheckTokens(const nsTArray<nsString>& aStr);
void RemoveDuplicatesInternal(nsTArray<nsCOMPtr<nsIAtom>>* aArray,
uint32_t aStart);
void AddInternal(const nsAttrValue* aAttr,
const nsTArray<nsString>& aTokens);
void RemoveInternal(const nsAttrValue* aAttr,

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

@ -3049,13 +3049,20 @@ nsDOMWindowUtils::IsPartOfOpaqueLayer(nsIDOMElement* aElement, bool* aResult)
return NS_ERROR_FAILURE;
}
PaintedLayer* layer = FrameLayerBuilder::GetDebugSingleOldPaintedLayerForFrame(frame);
if (!layer) {
return NS_ERROR_FAILURE;
ColorLayer* colorLayer = FrameLayerBuilder::GetDebugSingleOldLayerForFrame<ColorLayer>(frame);
if (colorLayer) {
auto color = colorLayer->GetColor();
*aResult = color.a == 1.0f;
return NS_OK;
}
*aResult = (layer->GetContentFlags() & Layer::CONTENT_OPAQUE);
return NS_OK;
PaintedLayer* paintedLayer = FrameLayerBuilder::GetDebugSingleOldLayerForFrame<PaintedLayer>(frame);
if (paintedLayer) {
*aResult = paintedLayer->IsOpaque();
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
@ -3078,7 +3085,7 @@ nsDOMWindowUtils::NumberOfAssignedPaintedLayers(nsIDOMElement** aElements,
return NS_ERROR_FAILURE;
}
PaintedLayer* layer = FrameLayerBuilder::GetDebugSingleOldPaintedLayerForFrame(frame);
PaintedLayer* layer = FrameLayerBuilder::GetDebugSingleOldLayerForFrame<PaintedLayer>(frame);
if (!layer) {
return NS_ERROR_FAILURE;
}

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

@ -2957,9 +2957,26 @@ nsFrameLoader::TryRemoteBrowser()
nsresult rv = GetNewTabContext(&context);
NS_ENSURE_SUCCESS(rv, false);
uint64_t nextTabParentId = 0;
if (mOwnerContent) {
nsAutoString nextTabParentIdAttr;
mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nextTabParentId,
nextTabParentIdAttr);
nextTabParentId = strtoull(NS_ConvertUTF16toUTF8(nextTabParentIdAttr).get(),
nullptr, 10);
// We may be in a window that was just opened, so try the
// nsIBrowserDOMWindow API as a backup.
if (!nextTabParentId && window) {
Unused << window->GetNextTabParentId(&nextTabParentId);
}
}
nsCOMPtr<Element> ownerElement = mOwnerContent;
mRemoteBrowser = ContentParent::CreateBrowser(context, ownerElement,
openerContentParent, sameTabGroupAs);
openerContentParent,
sameTabGroupAs,
nextTabParentId);
if (!mRemoteBrowser) {
return false;
}

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

@ -652,6 +652,7 @@ GK_ATOM(never, "never")
GK_ATOM(_new, "new")
GK_ATOM(newline, "newline")
GK_ATOM(nextBidi, "NextBidi")
GK_ATOM(nextTabParentId, "nextTabParentId")
GK_ATOM(no, "no")
GK_ATOM(noautofocus, "noautofocus")
GK_ATOM(noautohide, "noautohide")

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

@ -11563,7 +11563,14 @@ nsGlobalWindow::ShowSlowScriptDialog()
// Check if we should offer the option to debug
JS::AutoFilename filename;
unsigned lineno;
bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, &lineno);
// Computing the line number can be very expensive (see bug 1330231 for
// example), and we don't use the line number anywhere except than in the
// parent process, so we avoid computing it elsewhere. This gives us most of
// the wins we are interested in, since the source of the slowness here is
// minified scripts which is more common in Web content that is loaded in the
// content process.
unsigned* linenop = XRE_IsParentProcess() ? &lineno : nullptr;
bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, linenop);
// Record the slow script event if we haven't done so already for this inner window
// (which represents a particular page to the user).
@ -11579,8 +11586,7 @@ nsGlobalWindow::ShowSlowScriptDialog()
nsIDocShell* docShell = GetDocShell();
nsCOMPtr<nsITabChild> child = docShell ? docShell->GetTabChild() : nullptr;
action = monitor->NotifySlowScript(child,
filename.get(),
lineno);
filename.get());
if (action == ProcessHangMonitor::Terminate) {
return KillSlowScript;
}

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

@ -34,7 +34,6 @@ skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s
[browser_state_notifications.js]
skip-if = true # Bug 1271028
[browser_use_counters.js]
[browser_bug1307747.js]
[browser_timeout_throttling_with_audio_playback.js]
[browser_bug1303838.js]
[browser_inputStream_structuredClone.js]

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

@ -1,32 +0,0 @@
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
var {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
const BASE_URI = "http://mochi.test:8888/browser/dom/base/test/empty.html";
add_task(function* test_initialize() {
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
let browser = gBrowser.getBrowserForTab(tab);
let blob = yield ContentTask.spawn(browser, null, function() {
let blob = new Blob([new Array(1024*1024).join('123456789ABCDEF')]);
return blob;
});
ok(blob, "We have a blob!");
is(blob.size, new Array(1024*1024).join('123456789ABCDEF').length, "The size matches");
let status = yield ContentTask.spawn(browser, blob, function(blob) {
return new Promise(resolve => {
let fr = new content.FileReader();
fr.readAsText(blob);
fr.onloadend = function() {
resolve(fr.result == new Array(1024*1024).join('123456789ABCDEF'));
}
});
});
ok(status, "Data match!");
yield BrowserTestUtils.removeTab(tab);
});

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

@ -0,0 +1,151 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DOMWindowUtils test with animation</title>
</head>
<body>
<script type="application/javascript">
const SimpleTest = window.opener.SimpleTest;
const utils = SpecialPowers.getDOMWindowUtils(window);
const next = window.opener.next;
const is = window.opener.is;
const ok = window.opener.ok;
function addStyle(rules) {
const extraStyle = document.createElement("style");
document.head.appendChild(extraStyle);
rules.forEach(rule => {
extraStyle.sheet.insertRule(rule, extraStyle.sheet.cssRules.length);
});
}
function deleteStyle() {
document.head.querySelector("style").remove();
}
function test_getUnanimatedComputedStyle() {
[
{
property: "opacity",
keyframes: [1, 0],
expectedInitialStyle: "1",
expectedDuringTransitionStyle: "0",
isDiscrete: false,
},
{
property: "clear",
keyframes: ["left", "inline-end"],
expectedInitialStyle: "none",
expectedDuringTransitionStyle: "inline-end",
isDiscrete: true,
},
].forEach(testcase => {
const { property, keyframes, expectedInitialStyle,
expectedDuringTransitionStyle, isDiscrete } = testcase;
[null, "unset", "initial", "inherit"].forEach(initialStyle => {
const scriptAnimation = target => {
return target.animate({ [property]: keyframes }, 1000);
}
checkUnanimatedComputedStyle(property, initialStyle, null,
expectedInitialStyle, expectedInitialStyle,
scriptAnimation, "script animation");
const cssAnimationStyle = `@keyframes cssanimation {`
+ ` from { ${property}: ${ keyframes[0] }; }`
+ ` to { ${property}: ${ keyframes[1] }; } }`;
addStyle([cssAnimationStyle]);
const cssAnimation = target => {
target.style.animation = "cssanimation 1s";
return target.getAnimations()[0];
}
checkUnanimatedComputedStyle(property, initialStyle, null,
expectedInitialStyle, expectedInitialStyle,
cssAnimation, "CSS Animations");
deleteStyle();
// We don't support discrete animations for CSS Transitions yet.
// (bug 1320854)
if (!isDiscrete) {
const cssTransition = target => {
target.style[property] = keyframes[0];
target.style.transition =
`${ property } 1s`;
window.getComputedStyle(target)[property];
target.style[property] = keyframes[1];
return target.getAnimations()[0];
}
checkUnanimatedComputedStyle(property, initialStyle, null,
expectedInitialStyle,
expectedDuringTransitionStyle,
cssTransition, "CSS Transitions");
}
addStyle([cssAnimationStyle,
".pseudo::before { animation: cssanimation 1s; }"]);
const pseudoAnimation = target => {
target.classList.add("pseudo");
return target.getAnimations({ subtree: true })[0];
}
checkUnanimatedComputedStyle(property, initialStyle, "::before",
expectedInitialStyle, expectedInitialStyle,
pseudoAnimation, "Animation at pseudo");
deleteStyle();
});
});
SimpleTest.doesThrow(
() => utils.getUnanimatedComputedStyle(div, null, "background"),
"NS_ERROR_INVALID_ARG",
"Shorthand property should throw");
SimpleTest.doesThrow(
() => utils.getUnanimatedComputedStyle(div, null, "invalid"),
"NS_ERROR_INVALID_ARG",
"Invalid property should throw");
SimpleTest.doesThrow(
() => utils.getUnanimatedComputedStyle(null, null, "opacity"),
"NS_ERROR_INVALID_ARG",
"Null element should throw");
next();
window.close();
}
function checkUnanimatedComputedStyle(property, initialStyle, pseudoType,
expectedBeforeAnimation,
expectedDuringAnimation,
animate, animationType) {
const div = document.createElement("div");
document.body.appendChild(div);
if (initialStyle) {
div.style[property] = initialStyle;
}
is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
expectedBeforeAnimation,
`'${ property }' property with '${ initialStyle }' style `
+ `should be '${ expectedBeforeAnimation }' `
+ `before animating by ${ animationType }`);
const animation = animate(div);
animation.currentTime = 500;
is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
expectedDuringAnimation,
`'${ property }' property with '${ initialStyle }' style `
+ `should be '${ expectedDuringAnimation }' `
+ `even while animating by ${ animationType }`);
div.remove();
}
window.addEventListener("load", test_getUnanimatedComputedStyle);
</script>
</body>
</html>

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

@ -126,6 +126,7 @@ support-files =
file_bug907892.html
file_bug945152.jar
file_bug1274806.html
file_domwindowutils_animation.html
file_general_document.html
file_htmlserializer_1.html
file_htmlserializer_1_bodyonly.html

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

@ -152,122 +152,12 @@ function test_getAnimationType() {
}
function test_getUnanimatedComputedStyle() {
[
{
property: "opacity",
keyframes: [1, 0],
expectedInitialStyle: "1",
expectedDuringTransitionStyle: "0",
isDiscrete: false,
},
{
property: "clear",
keyframes: ["left", "inline-end"],
expectedInitialStyle: "none",
expectedDuringTransitionStyle: "inline-end",
isDiscrete: true,
},
].forEach(testcase => {
const { property, keyframes, expectedInitialStyle,
expectedDuringTransitionStyle, isDiscrete } = testcase;
[null, "unset", "initial", "inherit"].forEach(initialStyle => {
const scriptAnimation = target => {
return target.animate({ [property]: keyframes }, 1000);
}
checkUnanimatedComputedStyle(property, initialStyle, null,
expectedInitialStyle, expectedInitialStyle,
scriptAnimation, "script animation");
const cssAnimationStyle = `@keyframes cssanimation {`
+ ` from { ${property}: ${ keyframes[0] }; }`
+ ` to { ${property}: ${ keyframes[1] }; } }`;
document.styleSheets[0].insertRule(cssAnimationStyle, 0);
const cssAnimation = target => {
target.style.animation = "cssanimation 1s";
return target.getAnimations()[0];
}
checkUnanimatedComputedStyle(property, initialStyle, null,
expectedInitialStyle, expectedInitialStyle,
cssAnimation, "CSS Animations");
document.styleSheets[0].deleteRule(0);
// We don't support discrete animations for CSS Transitions yet.
// (bug 1320854)
if (!isDiscrete) {
const cssTransition = target => {
target.style[property] = keyframes[0];
target.style.transition =
`${ property } 1s`;
window.getComputedStyle(target)[property];
target.style[property] = keyframes[1];
return target.getAnimations()[0];
}
checkUnanimatedComputedStyle(property, initialStyle, null,
expectedInitialStyle,
expectedDuringTransitionStyle,
cssTransition, "CSS Transitions");
}
document.styleSheets[0].insertRule(cssAnimationStyle, 0);
document.styleSheets[0].insertRule(
".pseudo::before { animation: cssanimation 1s; }", 0);
const pseudoAnimation = target => {
target.classList.add("pseudo");
return target.getAnimations({ subtree: true })[0];
}
checkUnanimatedComputedStyle(property, initialStyle, "::before",
expectedInitialStyle, expectedInitialStyle,
pseudoAnimation, "Animation at pseudo");
document.styleSheets[0].deleteRule(0);
document.styleSheets[0].deleteRule(0);
});
});
SimpleTest.doesThrow(
() => utils.getUnanimatedComputedStyle(div, null, "background"),
"NS_ERROR_INVALID_ARG",
"Shorthand property should throw");
SimpleTest.doesThrow(
() => utils.getUnanimatedComputedStyle(div, null, "invalid"),
"NS_ERROR_INVALID_ARG",
"Invalid property should throw");
SimpleTest.doesThrow(
() => utils.getUnanimatedComputedStyle(null, null, "opacity"),
"NS_ERROR_INVALID_ARG",
"Null element should throw");
next();
}
function checkUnanimatedComputedStyle(property, initialStyle, pseudoType,
expectedBeforeAnimation,
expectedDuringAnimation,
animate, animationType) {
const div = document.createElement("div");
document.body.appendChild(div);
if (initialStyle) {
div.style[property] = initialStyle;
}
is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
expectedBeforeAnimation,
`'${ property }' property with '${ initialStyle }' style `
+ `should be '${ expectedBeforeAnimation }' `
+ `before animating by ${ animationType }`);
const animation = animate(div);
animation.currentTime = 500;
is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
expectedDuringAnimation,
`'${ property }' property with '${ initialStyle }' style `
+ `should be '${ expectedDuringAnimation }' `
+ `even while animating by ${ animationType }`);
div.remove();
SpecialPowers.pushPrefEnv(
{ set: [["dom.animations-api.core.enabled", true]] },
() => {
window.open("file_domwindowutils_animation.html");
}
);
}
var tests = [

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

@ -7,8 +7,9 @@
#include "BroadcastChannelParent.h"
#include "BroadcastChannelService.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/ipc/BlobParent.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/Unused.h"
#include "nsIScriptSecurityManager.h"
@ -73,30 +74,5 @@ BroadcastChannelParent::ActorDestroy(ActorDestroyReason aWhy)
}
}
void
BroadcastChannelParent::Deliver(const ClonedMessageData& aData)
{
AssertIsOnBackgroundThread();
// Duplicate the data for this parent.
ClonedMessageData newData(aData);
// Create new BlobParent objects for this message.
for (uint32_t i = 0, len = newData.blobsParent().Length(); i < len; ++i) {
RefPtr<BlobImpl> impl =
static_cast<BlobParent*>(newData.blobsParent()[i])->GetBlobImpl();
PBlobParent* blobParent =
BackgroundParent::GetOrCreateActorForBlobImpl(Manager(), impl);
if (!blobParent) {
return;
}
newData.blobsParent()[i] = blobParent;
}
Unused << SendNotify(newData);
}
} // namespace dom
} // namespace mozilla

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

@ -26,9 +26,6 @@ class BroadcastChannelParent final : public PBroadcastChannelParent
typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
public:
void Deliver(const ClonedMessageData& aData);
private:
explicit BroadcastChannelParent(const nsAString& aOriginChannelKey);
~BroadcastChannelParent();

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

@ -7,7 +7,7 @@
#include "BroadcastChannelService.h"
#include "BroadcastChannelParent.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/ipc/BlobParent.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/ipc/BackgroundParent.h"
#ifdef XP_WIN
@ -106,25 +106,43 @@ BroadcastChannelService::PostMessage(BroadcastChannelParent* aParent,
}
// We need to keep the array alive for the life-time of this operation.
nsTArray<RefPtr<BlobImpl>> blobs;
if (!aData.blobsParent().IsEmpty()) {
blobs.SetCapacity(aData.blobsParent().Length());
nsTArray<RefPtr<BlobImpl>> blobImpls;
if (!aData.blobs().IsEmpty()) {
blobImpls.SetCapacity(aData.blobs().Length());
for (uint32_t i = 0, len = aData.blobsParent().Length(); i < len; ++i) {
RefPtr<BlobImpl> impl =
static_cast<BlobParent*>(aData.blobsParent()[i])->GetBlobImpl();
MOZ_ASSERT(impl);
blobs.AppendElement(impl);
for (uint32_t i = 0, len = aData.blobs().Length(); i < len; ++i) {
RefPtr<BlobImpl> impl = IPCBlobUtils::Deserialize(aData.blobs()[i]);
MOZ_ASSERT(impl);
blobImpls.AppendElement(impl);
}
}
// For each parent actor, we notify the message.
for (uint32_t i = 0; i < parents->Length(); ++i) {
BroadcastChannelParent* parent = parents->ElementAt(i);
MOZ_ASSERT(parent);
if (parent != aParent) {
parent->Deliver(aData);
if (parent == aParent) {
continue;
}
// We need to have a copy of the data for this parent.
ClonedMessageData newData(aData);
MOZ_ASSERT(blobImpls.Length() == newData.blobs().Length());
if (!blobImpls.IsEmpty()) {
// Serialize Blob objects for this message.
for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) {
nsresult rv = IPCBlobUtils::Serialize(blobImpls[i], parent->Manager(),
newData.blobs()[i]);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
}
Unused << parent->SendNotify(newData);
}
}

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

@ -6,6 +6,7 @@ include protocol PBackground;
include protocol PBlob;
include protocol PChildToParentStream;
include protocol PFileDescriptorSet;
include protocol PIPCBlobInputStream;
include protocol PParentToChildStream;
include DOMTypes;

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

@ -5579,15 +5579,18 @@ CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& aWindow, double aX,
return;
}
// Flush layout updates
if (!(aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) {
nsContentUtils::FlushLayoutForTree(aWindow.AsInner()->GetOuterWindow());
}
CompositionOp op = UsedOperation();
bool discardContent = GlobalAlpha() == 1.0f
&& (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
const gfx::Rect drawRect(aX, aY, aW, aH);
EnsureTarget(discardContent ? &drawRect : nullptr);
// Flush layout updates
if (!(aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) {
nsContentUtils::FlushLayoutForTree(aWindow.AsInner()->GetOuterWindow());
if (!IsTargetValid()) {
return;
}
RefPtr<nsPresContext> presContext;

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

@ -223,8 +223,8 @@ TouchEvent::PrefEnabled(nsIDocShell* aDocShell)
if (enabled && aDocShell) {
// APZ might be disabled on this particular widget, in which case
// TouchEvent support will also be disabled. Try to detect that.
nsPresContext* pc = nullptr;
aDocShell->GetPresContext(&pc);
RefPtr<nsPresContext> pc;
aDocShell->GetPresContext(getter_AddRefs(pc));
if (pc && pc->GetRootWidget()) {
enabled &= pc->GetRootWidget()->AsyncPanZoomEnabled();
}

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

@ -8,34 +8,38 @@
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="target" style="width: 50px; height: 50px; background: green"></div>
<script>
SimpleTest.waitForExplicitFinish();
var target = document.getElementById("target");
target.addEventListener("pointerdown", () => {
target.requestFullscreen();
target.addEventListener("pointerdown", () => {
document.exitFullscreen();
}, {once: true});
}, {once: true});
document.addEventListener("fullscreenchange", () => {
if (document.fullscreenElement) {
ok(document.fullscreenElement, target, "fullscreenElement should be the div element");
// synthesize mouse events to generate pointer events and leave full screen.
synthesizeMouseAtCenter(target, { type: "mousedown" });
synthesizeMouseAtCenter(target, { type: "mouseup" });
} else {
SimpleTest.finish();
}
});
function startTest() {
// synthesize mouse events to generate pointer events and enter full screen.
synthesizeMouseAtCenter(target, { type: "mousedown" });
synthesizeMouseAtCenter(target, { type: "mouseup" });
let win = window.open("data:text/html,<body><div id='target' style='width: 50px; height: 50px; background: green'></div></body>", "_blank");
win.addEventListener("load", () => {
let target = win.document.getElementById("target");
target.addEventListener("pointerdown", () => {
target.requestFullscreen();
target.addEventListener("pointerdown", () => {
win.document.exitFullscreen();
}, {once: true});
}, {once: true});
win.document.addEventListener("fullscreenchange", () => {
if (win.document.fullscreenElement) {
ok(win.document.fullscreenElement, target, "fullscreenElement should be the div element");
// synthesize mouse events to generate pointer events and leave full screen.
synthesizeMouseAtCenter(target, { type: "mousedown" }, win);
synthesizeMouseAtCenter(target, { type: "mouseup" }, win);
} else {
win.close();
SimpleTest.finish();
}
});
// Make sure our window is focused before starting the test
SimpleTest.waitForFocus(() => {
// synthesize mouse events to generate pointer events and enter full screen.
synthesizeMouseAtCenter(target, { type: "mousedown" }, win);
synthesizeMouseAtCenter(target, { type: "mouseup" }, win);
}, win);
});
}
SimpleTest.waitForFocus(() => {
@ -47,7 +51,6 @@ SimpleTest.waitForFocus(() => {
]
}, startTest);
});
</script>
</body>
</html>

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

@ -290,10 +290,20 @@ FileReader::DoReadData(uint64_t aCount)
mResult.GetMutableData(&buf, oldLen + aCount, fallible);
NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
nsresult rv;
// nsFileStreams do not implement ReadSegment. In case here we are dealing
// with a nsIAsyncInputStream, in content process, we need to wrap a
// nsIBufferedInputStream around it.
if (!mBufferedStream) {
rv = NS_NewBufferedInputStream(getter_AddRefs(mBufferedStream),
mAsyncStream, 8192);
NS_ENSURE_SUCCESS(rv, rv);
}
uint32_t bytesRead = 0;
nsresult rv =
mAsyncStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
&bytesRead);
rv = mBufferedStream->ReadSegments(ReadFuncBinaryString, buf + oldLen,
aCount, &bytesRead);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -350,6 +360,7 @@ FileReader::ReadFileContent(Blob& aBlob,
mResultArrayBuffer = nullptr;
mAsyncStream = nullptr;
mBufferedStream = nullptr;
mTransferred = 0;
mTotal = 0;
@ -374,27 +385,30 @@ FileReader::ReadFileContent(Blob& aBlob,
return;
}
nsCOMPtr<nsITransport> transport;
aRv = sts->CreateInputTransport(stream,
/* aStartOffset */ 0,
/* aReadLimit */ -1,
/* aCloseWhenDone */ true,
getter_AddRefs(transport));
if (NS_WARN_IF(aRv.Failed())) {
return;
mAsyncStream = do_QueryInterface(stream);
if (!mAsyncStream) {
nsCOMPtr<nsITransport> transport;
aRv = sts->CreateInputTransport(stream,
/* aStartOffset */ 0,
/* aReadLimit */ -1,
/* aCloseWhenDone */ true,
getter_AddRefs(transport));
if (NS_WARN_IF(aRv.Failed())) {
return;
}
nsCOMPtr<nsIInputStream> wrapper;
aRv = transport->OpenInputStream(/* aFlags */ 0,
/* aSegmentSize */ 0,
/* aSegmentCount */ 0,
getter_AddRefs(wrapper));
if (NS_WARN_IF(aRv.Failed())) {
return;
}
mAsyncStream = do_QueryInterface(wrapper);
}
nsCOMPtr<nsIInputStream> wrapper;
aRv = transport->OpenInputStream(/* aFlags */ 0,
/* aSegmentSize */ 0,
/* aSegmentCount */ 0,
getter_AddRefs(wrapper));
if (NS_WARN_IF(aRv.Failed())) {
return;
}
MOZ_ASSERT(!mAsyncStream);
mAsyncStream = do_QueryInterface(wrapper);
MOZ_ASSERT(mAsyncStream);
mTotal = mBlob->GetSize(aRv);
@ -529,6 +543,7 @@ FileReader::FreeDataAndDispatchSuccess()
FreeFileData();
mResult.SetIsVoid(false);
mAsyncStream = nullptr;
mBufferedStream = nullptr;
mBlob = nullptr;
// Dispatch event to signify end of a successful operation
@ -544,6 +559,7 @@ FileReader::FreeDataAndDispatchError()
FreeFileData();
mResult.SetIsVoid(true);
mAsyncStream = nullptr;
mBufferedStream = nullptr;
mBlob = nullptr;
// Dispatch error event to signify load failure
@ -729,6 +745,7 @@ FileReader::Abort()
mResultArrayBuffer = nullptr;
mAsyncStream = nullptr;
mBufferedStream = nullptr;
mBlob = nullptr;
//Clean up memory buffer
@ -782,6 +799,11 @@ FileReader::Shutdown()
mAsyncStream = nullptr;
}
if (mBufferedStream) {
mBufferedStream->Close();
mBufferedStream = nullptr;
}
FreeFileData();
mResultArrayBuffer = nullptr;

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

@ -180,6 +180,7 @@ private:
bool mTimerIsActive;
nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
nsCOMPtr<nsIInputStream> mBufferedStream;
RefPtr<DOMError> mError;

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