зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound. a=merge
This commit is contained in:
Коммит
cbef4f34fc
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8bc59310552179f9a8bc6cdd0188e2475df52fb7"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5bb657ada461be666c35f419dbe072ed2ce632fc"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="07c383a786f188904311a37f6062c2cb84c9b61d">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8bc59310552179f9a8bc6cdd0188e2475df52fb7"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"git": {
|
||||
"git_revision": "bbf14824939479074049ff4c3888614794c4cec1",
|
||||
"git_revision": "088f350b39baf8f86c7c1161fd4be178ce822b7b",
|
||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "a032af293d40c761c90853a6ffc1e9e3692f3ecd",
|
||||
"revision": "97666dae0fe5da2a0da4f57f41fcb12e9c2fe709",
|
||||
"repo_path": "integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5bb657ada461be666c35f419dbe072ed2ce632fc"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="07c383a786f188904311a37f6062c2cb84c9b61d">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bbf14824939479074049ff4c3888614794c4cec1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="088f350b39baf8f86c7c1161fd4be178ce822b7b"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9f45c1988fe72749f0659409e6e3320fabf7b79a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -126,6 +126,9 @@ let TrackingProtection = {
|
|||
// Telemetry for disable protection.
|
||||
this.eventsHistogram.add(1);
|
||||
|
||||
// Hide the control center.
|
||||
document.getElementById("identity-popup").hidePopup();
|
||||
|
||||
BrowserReload();
|
||||
},
|
||||
|
||||
|
@ -146,6 +149,9 @@ let TrackingProtection = {
|
|||
// Telemetry for enable protection.
|
||||
this.eventsHistogram.add(2);
|
||||
|
||||
// Hide the control center.
|
||||
document.getElementById("identity-popup").hidePopup();
|
||||
|
||||
BrowserReload();
|
||||
},
|
||||
|
||||
|
|
|
@ -1139,7 +1139,9 @@ var gBrowserInit = {
|
|||
|
||||
gBrowser.addEventListener("AboutTabCrashedLoad", function(event) {
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
TabCrashReporter.onAboutTabCrashedLoad(gBrowser.getBrowserForDocument(event.target));
|
||||
TabCrashReporter.onAboutTabCrashedLoad(gBrowser.getBrowserForDocument(event.target), {
|
||||
crashedTabCount: SessionStore.crashedTabCount,
|
||||
});
|
||||
#endif
|
||||
}, false, true);
|
||||
|
||||
|
@ -1171,11 +1173,7 @@ var gBrowserInit = {
|
|||
SessionStore.reviveCrashedTab(tab);
|
||||
break;
|
||||
case "restoreAll":
|
||||
for (let browserWin of browserWindows()) {
|
||||
for (let tab of browserWin.gBrowser.tabs) {
|
||||
SessionStore.reviveCrashedTab(tab);
|
||||
}
|
||||
}
|
||||
SessionStore.reviveAllCrashedTabs();
|
||||
break;
|
||||
}
|
||||
}, false, true);
|
||||
|
@ -2411,8 +2409,10 @@ function BrowserViewSource(browser) {
|
|||
// doc - document to use for source, or null for this window's document
|
||||
// initialTab - name of the initial tab to display, or null for the first tab
|
||||
// imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
|
||||
function BrowserPageInfo(doc, initialTab, imageElement) {
|
||||
var args = {doc: doc, initialTab: initialTab, imageElement: imageElement};
|
||||
// frameOuterWindowID - the id of the frame that the context menu opened in; can be null/omitted
|
||||
function BrowserPageInfo(doc, initialTab, imageElement, frameOuterWindowID) {
|
||||
var args = {doc: doc, initialTab: initialTab, imageElement: imageElement,
|
||||
frameOuterWindowID: frameOuterWindowID};
|
||||
var windows = Services.wm.getEnumerator("Browser:page-info");
|
||||
|
||||
var documentURL = doc ? doc.location : window.gBrowser.selectedBrowser.contentDocumentAsCPOW.location;
|
||||
|
@ -6633,6 +6633,7 @@ var gIdentityHandler = {
|
|||
IDENTITY_MODE_MIXED_ACTIVE_BLOCKED : "verifiedDomain mixedContent mixedActiveBlocked", // SSL with unauthenticated active content blocked; no unauthenticated display content
|
||||
IDENTITY_MODE_MIXED_ACTIVE_BLOCKED_IDENTIFIED : "verifiedIdentity mixedContent mixedActiveBlocked", // SSL with unauthenticated active content blocked; no unauthenticated display content
|
||||
IDENTITY_MODE_CHROMEUI : "chromeUI", // Part of the product's UI
|
||||
IDENTITY_MODE_FILE_URI : "fileURI", // File path
|
||||
|
||||
// Cache the most recent SSLStatus and Location seen in checkIdentity
|
||||
_lastStatus : null,
|
||||
|
@ -6848,7 +6849,20 @@ var gIdentityHandler = {
|
|||
this.setMode(this.IDENTITY_MODE_USES_WEAK_CIPHER);
|
||||
}
|
||||
} else {
|
||||
this.setMode(this.IDENTITY_MODE_UNKNOWN);
|
||||
// Create a channel for the sole purpose of getting the resolved URI
|
||||
// of the request to determine if it's loaded from the file system.
|
||||
let resolvedURI = NetUtil.newChannel({uri,loadUsingSystemPrincipal:true}).URI;
|
||||
if (resolvedURI.schemeIs("jar")) {
|
||||
// Given a URI "jar:<jar-file-uri>!/<jar-entry>"
|
||||
// create a new URI using <jar-file-uri>!/<jar-entry>
|
||||
resolvedURI = NetUtil.newURI(resolvedURI.path);
|
||||
}
|
||||
|
||||
if (resolvedURI.schemeIs("file")) {
|
||||
this.setMode(this.IDENTITY_MODE_FILE_URI);
|
||||
} else {
|
||||
this.setMode(this.IDENTITY_MODE_UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
// Show the doorhanger when:
|
||||
|
|
|
@ -39,6 +39,8 @@ XPCOMUtils.defineLazyGetter(this, "PageMenuChild", function() {
|
|||
return new tmp.PageMenuChild();
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Feeds", "resource:///modules/Feeds.jsm");
|
||||
|
||||
// TabChildGlobal
|
||||
var global = this;
|
||||
|
||||
|
@ -840,3 +842,396 @@ addMessageListener("ContextMenu:SetAsDesktopBackground", (message) => {
|
|||
if (disable)
|
||||
sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result", { disable });
|
||||
});
|
||||
|
||||
let pageInfoListener = {
|
||||
|
||||
init: function(chromeGlobal) {
|
||||
chromeGlobal.addMessageListener("PageInfo:getData", this, false, true);
|
||||
},
|
||||
|
||||
receiveMessage: function(message) {
|
||||
this.imageViewRows = [];
|
||||
this.frameList = [];
|
||||
this.strings = message.data.strings;
|
||||
|
||||
let frameOuterWindowID = message.data.frameOuterWindowID;
|
||||
|
||||
// If inside frame then get the frame's window and document.
|
||||
if (frameOuterWindowID) {
|
||||
this.window = Services.wm.getOuterWindowWithId(frameOuterWindowID);
|
||||
this.document = this.window.document;
|
||||
}
|
||||
else {
|
||||
this.document = content.document;
|
||||
this.window = content.window;
|
||||
}
|
||||
|
||||
let pageInfoData = {metaViewRows: this.getMetaInfo(), docInfo: this.getDocumentInfo(),
|
||||
feeds: this.getFeedsInfo(), windowInfo: this.getWindowInfo()};
|
||||
sendAsyncMessage("PageInfo:data", pageInfoData);
|
||||
|
||||
// Separate step so page info dialog isn't blank while waiting for this to finish.
|
||||
this.getMediaInfo();
|
||||
|
||||
// Send the message after all the media elements have been walked through.
|
||||
let pageInfoMediaData = {imageViewRows: this.imageViewRows};
|
||||
|
||||
this.imageViewRows = null;
|
||||
this.frameList = null;
|
||||
this.strings = null;
|
||||
this.window = null;
|
||||
this.document = null;
|
||||
|
||||
sendAsyncMessage("PageInfo:mediaData", pageInfoMediaData);
|
||||
},
|
||||
|
||||
getMetaInfo: function() {
|
||||
let metaViewRows = [];
|
||||
|
||||
// Get the meta tags from the page.
|
||||
let metaNodes = this.document.getElementsByTagName("meta");
|
||||
|
||||
for (let metaNode of metaNodes) {
|
||||
metaViewRows.push([metaNode.name || metaNode.httpEquiv || metaNode.getAttribute("property"),
|
||||
metaNode.content]);
|
||||
}
|
||||
|
||||
return metaViewRows;
|
||||
},
|
||||
|
||||
getWindowInfo: function() {
|
||||
let windowInfo = {};
|
||||
windowInfo.isTopWindow = this.window == this.window.top;
|
||||
|
||||
let hostName = null;
|
||||
try {
|
||||
hostName = this.window.location.host;
|
||||
}
|
||||
catch (exception) { }
|
||||
|
||||
windowInfo.hostName = hostName;
|
||||
return windowInfo;
|
||||
},
|
||||
|
||||
getDocumentInfo: function() {
|
||||
let docInfo = {};
|
||||
docInfo.title = this.document.title;
|
||||
docInfo.location = this.document.location.toString();
|
||||
docInfo.referrer = this.document.referrer;
|
||||
docInfo.compatMode = this.document.compatMode;
|
||||
docInfo.contentType = this.document.contentType;
|
||||
docInfo.characterSet = this.document.characterSet;
|
||||
docInfo.lastModified = this.document.lastModified;
|
||||
|
||||
let documentURIObject = {};
|
||||
documentURIObject.spec = this.document.documentURIObject.spec;
|
||||
documentURIObject.originCharset = this.document.documentURIObject.originCharset;
|
||||
docInfo.documentURIObject = documentURIObject;
|
||||
|
||||
docInfo.isContentWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(content);
|
||||
|
||||
return docInfo;
|
||||
},
|
||||
|
||||
getFeedsInfo: function() {
|
||||
let feeds = [];
|
||||
// Get the feeds from the page.
|
||||
let linkNodes = this.document.getElementsByTagName("link");
|
||||
let length = linkNodes.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
let link = linkNodes[i];
|
||||
if (!link.href) {
|
||||
continue;
|
||||
}
|
||||
let rel = link.rel && link.rel.toLowerCase();
|
||||
let rels = {};
|
||||
|
||||
if (rel) {
|
||||
for each (let relVal in rel.split(/\s+/)) {
|
||||
rels[relVal] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) {
|
||||
let type = Feeds.isValidFeed(link, this.document.nodePrincipal, "feed" in rels);
|
||||
if (type) {
|
||||
type = this.strings[type] || this.strings["application/rss+xml"];
|
||||
feeds.push([link.title, type, link.href]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return feeds;
|
||||
},
|
||||
|
||||
// Only called once to get the media tab's media elements from the content page.
|
||||
// The actual work is done with a TreeWalker that calls doGrab() once for
|
||||
// each element node in the document.
|
||||
getMediaInfo: function()
|
||||
{
|
||||
this.goThroughFrames(this.document, this.window);
|
||||
this.processFrames();
|
||||
},
|
||||
|
||||
goThroughFrames: function(aDocument, aWindow)
|
||||
{
|
||||
this.frameList.push(aDocument);
|
||||
if (aWindow && aWindow.frames.length > 0) {
|
||||
let num = aWindow.frames.length;
|
||||
for (let i = 0; i < num; i++) {
|
||||
this.goThroughFrames(aWindow.frames[i].document, aWindow.frames[i]); // recurse through the frames
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
processFrames: function()
|
||||
{
|
||||
if (this.frameList.length) {
|
||||
let doc = this.frameList[0];
|
||||
let iterator = doc.createTreeWalker(doc, content.NodeFilter.SHOW_ELEMENT, elem => this.grabAll(elem));
|
||||
this.frameList.shift();
|
||||
this.doGrab(iterator);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This function's previous purpose in pageInfo.js was to get loop through 500 elements at a time.
|
||||
* The iterator filter will filter for media elements.
|
||||
* #TODO Bug 1175794: refactor pageInfo.js to receive a media element at a time
|
||||
* from messages and continually update UI.
|
||||
*/
|
||||
doGrab: function(iterator)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!iterator.nextNode()) {
|
||||
this.processFrames();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
grabAll: function(elem)
|
||||
{
|
||||
// Check for images defined in CSS (e.g. background, borders), any node may have multiple.
|
||||
let computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, "");
|
||||
|
||||
let addImage = (url, type, alt, elem, isBg) => {
|
||||
let element = this.serializeElementInfo(url, type, alt, elem, isBg);
|
||||
this.imageViewRows.push([url, type, alt, element, isBg]);
|
||||
};
|
||||
|
||||
if (computedStyle) {
|
||||
let addImgFunc = (label, val) => {
|
||||
if (val.primitiveType == content.CSSPrimitiveValue.CSS_URI) {
|
||||
addImage(val.getStringValue(), label, this.strings.notSet, elem, true);
|
||||
}
|
||||
else if (val.primitiveType == content.CSSPrimitiveValue.CSS_STRING) {
|
||||
// This is for -moz-image-rect.
|
||||
// TODO: Reimplement once bug 714757 is fixed.
|
||||
let strVal = val.getStringValue();
|
||||
if (strVal.search(/^.*url\(\"?/) > -1) {
|
||||
let url = strVal.replace(/^.*url\(\"?/,"").replace(/\"?\).*$/,"");
|
||||
addImage(url, label, this.strings.notSet, elem, true);
|
||||
}
|
||||
}
|
||||
else if (val.cssValueType == content.CSSValue.CSS_VALUE_LIST) {
|
||||
// Recursively resolve multiple nested CSS value lists.
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
addImgFunc(label, val.item(i));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addImgFunc(this.strings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image"));
|
||||
addImgFunc(this.strings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source"));
|
||||
addImgFunc(this.strings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image"));
|
||||
addImgFunc(this.strings.mediaCursor, computedStyle.getPropertyCSSValue("cursor"));
|
||||
}
|
||||
|
||||
// One swi^H^H^Hif-else to rule them all.
|
||||
if (elem instanceof content.HTMLImageElement) {
|
||||
addImage(elem.src, this.strings.mediaImg,
|
||||
(elem.hasAttribute("alt")) ? elem.alt : this.strings.notSet, elem, false);
|
||||
}
|
||||
else if (elem instanceof content.SVGImageElement) {
|
||||
try {
|
||||
// Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
|
||||
// or the URI formed from the baseURI and the URL is not a valid URI.
|
||||
let href = makeURLAbsolute(elem.baseURI, elem.href.baseVal);
|
||||
addImage(href, this.strings.mediaImg, "", elem, false);
|
||||
} catch (e) { }
|
||||
}
|
||||
else if (elem instanceof content.HTMLVideoElement) {
|
||||
addImage(elem.currentSrc, this.strings.mediaVideo, "", elem, false);
|
||||
}
|
||||
else if (elem instanceof content.HTMLAudioElement) {
|
||||
addImage(elem.currentSrc, this.strings.mediaAudio, "", elem, false);
|
||||
}
|
||||
else if (elem instanceof content.HTMLLinkElement) {
|
||||
if (elem.rel && /\bicon\b/i.test(elem.rel)) {
|
||||
addImage(elem.href, this.strings.mediaLink, "", elem, false);
|
||||
}
|
||||
}
|
||||
else if (elem instanceof content.HTMLInputElement || elem instanceof content.HTMLButtonElement) {
|
||||
if (elem.type.toLowerCase() == "image") {
|
||||
addImage(elem.src, this.strings.mediaInput,
|
||||
(elem.hasAttribute("alt")) ? elem.alt : this.strings.notSet, elem, false);
|
||||
}
|
||||
}
|
||||
else if (elem instanceof content.HTMLObjectElement) {
|
||||
addImage(elem.data, this.strings.mediaObject, this.getValueText(elem), elem, false);
|
||||
}
|
||||
else if (elem instanceof content.HTMLEmbedElement) {
|
||||
addImage(elem.src, this.strings.mediaEmbed, "", elem, false);
|
||||
}
|
||||
|
||||
return content.NodeFilter.FILTER_ACCEPT;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set up a JSON element object with all the instanceOf and other infomation that
|
||||
* makePreview in pageInfo.js uses to figure out how to display the preview.
|
||||
*/
|
||||
|
||||
serializeElementInfo: function(url, type, alt, item, isBG)
|
||||
{
|
||||
// Interface for image loading content.
|
||||
const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
|
||||
|
||||
let result = {};
|
||||
|
||||
let imageText;
|
||||
if (!isBG &&
|
||||
!(item instanceof content.SVGImageElement) &&
|
||||
!(this.document instanceof content.ImageDocument)) {
|
||||
imageText = item.title || item.alt;
|
||||
|
||||
if (!imageText && !(item instanceof content.HTMLImageElement)) {
|
||||
imageText = this.getValueText(item);
|
||||
}
|
||||
}
|
||||
|
||||
result.imageText = imageText;
|
||||
result.longDesc = item.longDesc;
|
||||
result.numFrames = 1;
|
||||
|
||||
if (item instanceof content.HTMLObjectElement ||
|
||||
item instanceof content.HTMLEmbedElement ||
|
||||
item instanceof content.HTMLLinkElement) {
|
||||
result.mimeType = item.type;
|
||||
}
|
||||
|
||||
if (!result.mimeType && !isBG && item instanceof nsIImageLoadingContent) {
|
||||
// Interface for image loading content.
|
||||
const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
|
||||
let imageRequest = item.getRequest(nsIImageLoadingContent.CURRENT_REQUEST);
|
||||
if (imageRequest) {
|
||||
result.mimeType = imageRequest.mimeType;
|
||||
let image = !(imageRequest.imageStatus & imageRequest.STATUS_ERROR) && imageRequest.image;
|
||||
if (image) {
|
||||
result.numFrames = image.numFrames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a data url, get the MIME type from the url
|
||||
if (!result.mimeType && url.startsWith("data:")) {
|
||||
let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url);
|
||||
if (dataMimeType)
|
||||
result.mimeType = dataMimeType[1].toLowerCase();
|
||||
}
|
||||
|
||||
result.HTMLLinkElement = item instanceof content.HTMLLinkElement;
|
||||
result.HTMLInputElement = item instanceof content.HTMLInputElement;
|
||||
result.HTMLImageElement = item instanceof content.HTMLImageElement;
|
||||
result.HTMLObjectElement = item instanceof content.HTMLObjectElement;
|
||||
result.SVGImageElement = item instanceof content.SVGImageElement;
|
||||
result.HTMLVideoElement = item instanceof content.HTMLVideoElement;
|
||||
result.HTMLAudioElement = item instanceof content.HTMLAudioElement;
|
||||
|
||||
if (!isBG) {
|
||||
result.width = item.width;
|
||||
result.height = item.height;
|
||||
}
|
||||
|
||||
if (item instanceof content.SVGImageElement) {
|
||||
result.SVGImageElementWidth = item.width.baseVal.value;
|
||||
result.SVGImageElementHeight = item.height.baseVal.value;
|
||||
}
|
||||
|
||||
result.baseURI = item.baseURI;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//******** Other Misc Stuff
|
||||
// Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
|
||||
// parse a node to extract the contents of the node
|
||||
getValueText: function(node)
|
||||
{
|
||||
|
||||
let valueText = "";
|
||||
|
||||
// Form input elements don't generally contain information that is useful to our callers, so return nothing.
|
||||
if (node instanceof content.HTMLInputElement ||
|
||||
node instanceof content.HTMLSelectElement ||
|
||||
node instanceof content.HTMLTextAreaElement) {
|
||||
return valueText;
|
||||
}
|
||||
|
||||
// Otherwise recurse for each child.
|
||||
let length = node.childNodes.length;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
let childNode = node.childNodes[i];
|
||||
let nodeType = childNode.nodeType;
|
||||
|
||||
// Text nodes are where the goods are.
|
||||
if (nodeType == content.Node.TEXT_NODE) {
|
||||
valueText += " " + childNode.nodeValue;
|
||||
}
|
||||
// And elements can have more text inside them.
|
||||
else if (nodeType == content.Node.ELEMENT_NODE) {
|
||||
// Images are special, we want to capture the alt text as if the image weren't there.
|
||||
if (childNode instanceof content.HTMLImageElement) {
|
||||
valueText += " " + this.getAltText(childNode);
|
||||
}
|
||||
else {
|
||||
valueText += " " + this.getValueText(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.stripWS(valueText);
|
||||
},
|
||||
|
||||
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html.
|
||||
// Traverse the tree in search of an img or area element and grab its alt tag.
|
||||
getAltText: function(node)
|
||||
{
|
||||
let altText = "";
|
||||
|
||||
if (node.alt) {
|
||||
return node.alt;
|
||||
}
|
||||
let length = node.childNodes.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if ((altText = this.getAltText(node.childNodes[i]) != undefined)) { // stupid js warning...
|
||||
return altText;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html.
|
||||
// Strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space.
|
||||
stripWS: function(text)
|
||||
{
|
||||
let middleRE = /\s+/g;
|
||||
let endRE = /(^\s+)|(\s+$)/g;
|
||||
|
||||
text = text.replace(middleRE, " ");
|
||||
return text.replace(endRE, "");
|
||||
}
|
||||
};
|
||||
pageInfoListener.init(this);
|
|
@ -96,10 +96,6 @@ ContentSearchUIController.prototype = {
|
|||
|
||||
set engines(val) {
|
||||
this._engines = val;
|
||||
if (!this._table.hidden) {
|
||||
this._setUpOneOffButtons();
|
||||
return;
|
||||
}
|
||||
this._pendingOneOffRefresh = true;
|
||||
},
|
||||
|
||||
|
@ -127,6 +123,9 @@ ContentSearchUIController.prototype = {
|
|||
let allElts = [...this._suggestionsList.children,
|
||||
...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
// If we are selecting a suggestion and a one-off is selected, don't deselect it.
|
||||
let excludeIndex = idx < this.numSuggestions && this.selectedButtonIndex > -1 ?
|
||||
this.numSuggestions + this.selectedButtonIndex : -1;
|
||||
for (let i = 0; i < allElts.length; ++i) {
|
||||
let elt = allElts[i];
|
||||
let ariaSelectedElt = i < this.numSuggestions ? elt.firstChild : elt;
|
||||
|
@ -135,16 +134,43 @@ ContentSearchUIController.prototype = {
|
|||
ariaSelectedElt.setAttribute("aria-selected", "true");
|
||||
this.input.setAttribute("aria-activedescendant", ariaSelectedElt.id);
|
||||
}
|
||||
else {
|
||||
else if (i != excludeIndex) {
|
||||
elt.classList.remove("selected");
|
||||
ariaSelectedElt.setAttribute("aria-selected", "false");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get selectedButtonIndex() {
|
||||
let elts = [...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
for (let i = 0; i < elts.length; ++i) {
|
||||
if (elts[i].classList.contains("selected")) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
set selectedButtonIndex(idx) {
|
||||
let elts = [...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
for (let i = 0; i < elts.length; ++i) {
|
||||
let elt = elts[i];
|
||||
if (i == idx) {
|
||||
elt.classList.add("selected");
|
||||
elt.setAttribute("aria-selected", "true");
|
||||
}
|
||||
else {
|
||||
elt.classList.remove("selected");
|
||||
elt.setAttribute("aria-selected", "false");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get selectedEngineName() {
|
||||
let selectedElt = this._table.querySelector(".selected");
|
||||
if (selectedElt && selectedElt.engineName) {
|
||||
let selectedElt = this._oneOffsTable.querySelector(".selected");
|
||||
if (selectedElt) {
|
||||
return selectedElt.engineName;
|
||||
}
|
||||
return this.defaultEngine.name;
|
||||
|
@ -194,7 +220,7 @@ ContentSearchUIController.prototype = {
|
|||
},
|
||||
|
||||
_onCommand: function(aEvent) {
|
||||
if (this.selectedIndex == this.numSuggestions + this._oneOffButtons.length) {
|
||||
if (this.selectedButtonIndex == this._oneOffButtons.length) {
|
||||
// Settings button was selected.
|
||||
this._sendMsg("ManageEngines");
|
||||
return;
|
||||
|
@ -264,19 +290,58 @@ ContentSearchUIController.prototype = {
|
|||
|
||||
_onKeypress: function (event) {
|
||||
let selectedIndexDelta = 0;
|
||||
let selectedSuggestionDelta = 0;
|
||||
let selectedOneOffDelta = 0;
|
||||
|
||||
switch (event.keyCode) {
|
||||
case event.DOM_VK_UP:
|
||||
if (!this._table.hidden) {
|
||||
selectedIndexDelta = -1;
|
||||
if (this._table.hidden) {
|
||||
return;
|
||||
}
|
||||
if (event.getModifierState("Accel")) {
|
||||
if (event.shiftKey) {
|
||||
selectedSuggestionDelta = -1;
|
||||
break;
|
||||
}
|
||||
this._cycleCurrentEngine(true);
|
||||
break;
|
||||
}
|
||||
if (event.altKey) {
|
||||
selectedOneOffDelta = -1;
|
||||
break;
|
||||
}
|
||||
selectedIndexDelta = -1;
|
||||
break;
|
||||
case event.DOM_VK_DOWN:
|
||||
if (this._table.hidden) {
|
||||
this._getSuggestions();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
selectedIndexDelta = 1;
|
||||
if (event.getModifierState("Accel")) {
|
||||
if (event.shiftKey) {
|
||||
selectedSuggestionDelta = 1;
|
||||
break;
|
||||
}
|
||||
this._cycleCurrentEngine(false);
|
||||
break;
|
||||
}
|
||||
if (event.altKey) {
|
||||
selectedOneOffDelta = 1;
|
||||
break;
|
||||
}
|
||||
selectedIndexDelta = 1;
|
||||
break;
|
||||
case event.DOM_VK_TAB:
|
||||
if (this._table.hidden) {
|
||||
return;
|
||||
}
|
||||
// Shift+tab when either the first or no one-off is selected, as well as
|
||||
// tab when the settings button is selected, should change focus as normal.
|
||||
if ((this.selectedButtonIndex <= 0 && event.shiftKey) ||
|
||||
this.selectedButtonIndex == this._oneOffButtons.length && !event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
selectedOneOffDelta = event.shiftKey ? -1 : 1;
|
||||
break;
|
||||
case event.DOM_VK_RIGHT:
|
||||
// Allow normal caret movement until the caret is at the end of the input.
|
||||
|
@ -297,37 +362,97 @@ ContentSearchUIController.prototype = {
|
|||
}
|
||||
this._stickyInputValue = this.input.value;
|
||||
this._hideSuggestions();
|
||||
break;
|
||||
return;
|
||||
case event.DOM_VK_RETURN:
|
||||
this._onCommand(event);
|
||||
break;
|
||||
return;
|
||||
case event.DOM_VK_DELETE:
|
||||
if (this.selectedIndex >= 0) {
|
||||
this.deleteSuggestionAtIndex(this.selectedIndex);
|
||||
}
|
||||
break;
|
||||
return;
|
||||
case event.DOM_VK_ESCAPE:
|
||||
if (!this._table.hidden) {
|
||||
this._hideSuggestions();
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
let currentIndex = this.selectedIndex;
|
||||
if (selectedIndexDelta) {
|
||||
// Update the selection.
|
||||
let newSelectedIndex = this.selectedIndex + selectedIndexDelta;
|
||||
let newSelectedIndex = currentIndex + selectedIndexDelta;
|
||||
if (newSelectedIndex < -1) {
|
||||
newSelectedIndex = this.numSuggestions + this._oneOffButtons.length;
|
||||
}
|
||||
else if (this.numSuggestions + this._oneOffButtons.length < newSelectedIndex) {
|
||||
// If are moving up from the first one off, we have to deselect the one off
|
||||
// manually because the selectedIndex setter tries to exclude the selected
|
||||
// one-off (which is desirable for accel+shift+up/down).
|
||||
if (currentIndex == this.numSuggestions && selectedIndexDelta == -1) {
|
||||
this.selectedButtonIndex = -1;
|
||||
}
|
||||
this.selectAndUpdateInput(newSelectedIndex);
|
||||
}
|
||||
|
||||
else if (selectedSuggestionDelta) {
|
||||
let newSelectedIndex;
|
||||
if (currentIndex >= this.numSuggestions || currentIndex == -1) {
|
||||
// No suggestion already selected, select the first/last one appropriately.
|
||||
newSelectedIndex = selectedSuggestionDelta == 1 ?
|
||||
0 : this.numSuggestions - 1;
|
||||
}
|
||||
else {
|
||||
newSelectedIndex = currentIndex + selectedSuggestionDelta;
|
||||
}
|
||||
if (newSelectedIndex >= this.numSuggestions) {
|
||||
newSelectedIndex = -1;
|
||||
}
|
||||
this.selectAndUpdateInput(newSelectedIndex);
|
||||
|
||||
// Prevent the input's caret from moving.
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
else if (selectedOneOffDelta) {
|
||||
let newSelectedIndex;
|
||||
let currentButton = this.selectedButtonIndex;
|
||||
if (currentButton == -1 || currentButton == this._oneOffButtons.length) {
|
||||
// No one-off already selected, select the first/last one appropriately.
|
||||
newSelectedIndex = selectedOneOffDelta == 1 ?
|
||||
0 : this._oneOffButtons.length - 1;
|
||||
}
|
||||
else {
|
||||
newSelectedIndex = currentButton + selectedOneOffDelta;
|
||||
}
|
||||
// Allow selection of the settings button via the tab key.
|
||||
if (newSelectedIndex == this._oneOffButtons.length &&
|
||||
event.keyCode != event.DOM_VK_TAB) {
|
||||
newSelectedIndex = -1;
|
||||
}
|
||||
this.selectedButtonIndex = newSelectedIndex;
|
||||
}
|
||||
|
||||
// Prevent the input's caret from moving.
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_currentEngineIndex: -1,
|
||||
_cycleCurrentEngine: function (aReverse) {
|
||||
if ((this._currentEngineIndex == this._oneOffButtons.length - 1 && !aReverse) ||
|
||||
(this._currentEngineIndex < 0 && aReverse)) {
|
||||
return;
|
||||
}
|
||||
this._currentEngineIndex += aReverse ? -1 : 1;
|
||||
let engine;
|
||||
if (this._currentEngineIndex == -1) {
|
||||
engine = this._originalDefaultEngine;
|
||||
} else {
|
||||
let button = this._oneOffButtons[this._currentEngineIndex];
|
||||
engine = {
|
||||
name: button.engineName,
|
||||
icon: button.firstChild.getAttribute("src"),
|
||||
};
|
||||
}
|
||||
this._sendMsg("SetCurrentEngine", engine.name);
|
||||
this.defaultEngine = engine;
|
||||
},
|
||||
|
||||
_onFocus: function () {
|
||||
|
@ -356,7 +481,12 @@ ContentSearchUIController.prototype = {
|
|||
},
|
||||
|
||||
_onMousemove: function (event) {
|
||||
this.selectedIndex = this._indexOfTableItem(event.target);
|
||||
let idx = this._indexOfTableItem(event.target);
|
||||
if (idx >= this.numSuggestions) {
|
||||
this.selectedButtonIndex = idx - this.numSuggestions;
|
||||
return;
|
||||
}
|
||||
this.selectedIndex = idx;
|
||||
},
|
||||
|
||||
_onMouseup: function (event) {
|
||||
|
@ -366,6 +496,15 @@ ContentSearchUIController.prototype = {
|
|||
this._onCommand(event);
|
||||
},
|
||||
|
||||
_onMouseout: function (event) {
|
||||
// We only deselect one-off buttons and the settings button when they are
|
||||
// moused out.
|
||||
let idx = this._indexOfTableItem(event.originalTarget);
|
||||
if (idx >= this.numSuggestions) {
|
||||
this.selectedButtonIndex = -1;
|
||||
}
|
||||
},
|
||||
|
||||
_onClick: function (event) {
|
||||
this._onMouseup(event);
|
||||
},
|
||||
|
@ -427,6 +566,10 @@ ContentSearchUIController.prototype = {
|
|||
}
|
||||
this._table.hidden = false;
|
||||
this.input.setAttribute("aria-expanded", "true");
|
||||
this._originalDefaultEngine = {
|
||||
name: this.defaultEngine.name,
|
||||
icon: this.defaultEngine.icon,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -447,10 +590,6 @@ ContentSearchUIController.prototype = {
|
|||
name: engine.name,
|
||||
icon: this._getFaviconURIFromBuffer(engine.iconBuffer),
|
||||
};
|
||||
if (!this._table.hidden) {
|
||||
this._setUpOneOffButtons();
|
||||
return;
|
||||
}
|
||||
this._pendingOneOffRefresh = true;
|
||||
},
|
||||
|
||||
|
@ -572,6 +711,9 @@ ContentSearchUIController.prototype = {
|
|||
|
||||
_hideSuggestions: function () {
|
||||
this.input.setAttribute("aria-expanded", "false");
|
||||
this.selectedIndex = -1;
|
||||
this.selectedButtonIndex = -1;
|
||||
this._currentEngineIndex = -1;
|
||||
this._table.hidden = true;
|
||||
},
|
||||
|
||||
|
@ -605,11 +747,7 @@ ContentSearchUIController.prototype = {
|
|||
document.addEventListener("mouseup", () => { delete this._mousedown; });
|
||||
|
||||
// Deselect the selected element on mouseout if it wasn't a suggestion.
|
||||
this._table.addEventListener("mouseout", () => {
|
||||
if (this.selectedIndex >= this.numSuggestions) {
|
||||
this.selectAndUpdateInput(-1);
|
||||
}
|
||||
});
|
||||
this._table.addEventListener("mouseout", this);
|
||||
|
||||
// If a search is loaded in the same tab, ensure the suggestions dropdown
|
||||
// is hidden immediately when the page starts loading and not when it first
|
||||
|
|
|
@ -1051,7 +1051,8 @@ nsContextMenu.prototype = {
|
|||
},
|
||||
|
||||
viewFrameInfo: function() {
|
||||
BrowserPageInfo(this.target.ownerDocument);
|
||||
BrowserPageInfo(this.target.ownerDocument, null, null,
|
||||
this.frameOuterWindowID);
|
||||
},
|
||||
|
||||
reloadImage: function() {
|
||||
|
|
|
@ -3,41 +3,11 @@
|
|||
* 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/. */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
|
||||
"resource:///modules/Feeds.jsm");
|
||||
|
||||
function initFeedTab()
|
||||
function initFeedTab(feeds)
|
||||
{
|
||||
const feedTypes = {
|
||||
"application/rss+xml": gBundle.getString("feedRss"),
|
||||
"application/atom+xml": gBundle.getString("feedAtom"),
|
||||
"text/xml": gBundle.getString("feedXML"),
|
||||
"application/xml": gBundle.getString("feedXML"),
|
||||
"application/rdf+xml": gBundle.getString("feedXML")
|
||||
};
|
||||
|
||||
// get the feeds
|
||||
var linkNodes = gDocument.getElementsByTagName("link");
|
||||
var length = linkNodes.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var link = linkNodes[i];
|
||||
if (!link.href)
|
||||
continue;
|
||||
|
||||
var rel = link.rel && link.rel.toLowerCase();
|
||||
var rels = {};
|
||||
if (rel) {
|
||||
for each (let relVal in rel.split(/\s+/))
|
||||
rels[relVal] = true;
|
||||
}
|
||||
|
||||
if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) {
|
||||
var type = Feeds.isValidFeed(link, gDocument.nodePrincipal, "feed" in rels);
|
||||
if (type) {
|
||||
type = feedTypes[type] || feedTypes["application/rss+xml"];
|
||||
addRow(link.title, type, link.href);
|
||||
}
|
||||
}
|
||||
for (let feed of feeds) {
|
||||
let [name, type, url] = feed;
|
||||
addRow(name, type, url);
|
||||
}
|
||||
|
||||
var feedListbox = document.getElementById("feedListbox");
|
||||
|
|
|
@ -52,8 +52,19 @@ pageInfoTreeView.prototype = {
|
|||
{
|
||||
this.rows = this.data.push(row);
|
||||
this.rowCountChanged(this.rows - 1, 1);
|
||||
if (this.selection.count == 0 && this.rowCount && !gImageElement)
|
||||
if (this.selection.count == 0 && this.rowCount && !gImageElement) {
|
||||
this.selection.select(0);
|
||||
}
|
||||
},
|
||||
|
||||
addRows: function(rows)
|
||||
{
|
||||
this.data = this.data.concat(rows);
|
||||
this.rowCountChanged(this.rows, rows.length);
|
||||
this.rows = this.data.length;
|
||||
if (this.selection.count == 0 && this.rowCount && !gImageElement) {
|
||||
this.selection.select(0);
|
||||
}
|
||||
},
|
||||
|
||||
rowCountChanged: function(index, count)
|
||||
|
@ -140,8 +151,7 @@ pageInfoTreeView.prototype = {
|
|||
};
|
||||
|
||||
// mmm, yummy. global variables.
|
||||
var gWindow = null;
|
||||
var gDocument = null;
|
||||
var gDocInfo = null;
|
||||
var gImageElement = null;
|
||||
|
||||
// column number to help using the data array
|
||||
|
@ -286,8 +296,7 @@ const XHTMLre = RegExp(XHTMLNSre + "|" + XHTML2NSre, "");
|
|||
* invoked as "XXXLoadFunc();"
|
||||
*/
|
||||
|
||||
// These functions are called to build the data displayed in the Page
|
||||
// Info window. The global variables gDocument and gWindow are set.
|
||||
// These functions are called to build the data displayed in the Page Info window.
|
||||
var onLoadRegistry = [ ];
|
||||
|
||||
// These functions are called to remove old data still displayed in
|
||||
|
@ -296,14 +305,6 @@ var onLoadRegistry = [ ];
|
|||
// tab is cleared.
|
||||
var onResetRegistry = [ ];
|
||||
|
||||
// These are called once for each subframe of the target document and
|
||||
// the target document itself. The frame is passed as an argument.
|
||||
var onProcessFrame = [ ];
|
||||
|
||||
// These functions are called once for each element (in all subframes, if any)
|
||||
// in the target document. The element is passed as an argument.
|
||||
var onProcessElement = [ ];
|
||||
|
||||
// These functions are called once when all the elements in all of the target
|
||||
// document (and all of its subframes, if any) have been processed
|
||||
var onFinished = [ ];
|
||||
|
@ -311,9 +312,6 @@ var onFinished = [ ];
|
|||
// These functions are called once when the Page Info window is closed.
|
||||
var onUnloadRegistry = [ ];
|
||||
|
||||
// These functions are called once when an image preview is shown.
|
||||
var onImagePreviewShown = [ ];
|
||||
|
||||
/* Called when PageInfo window is loaded. Arguments are:
|
||||
* window.arguments[0] - (optional) an object consisting of
|
||||
* - doc: (optional) document to use for source. if not provided,
|
||||
|
@ -341,11 +339,6 @@ function onLoadPageInfo()
|
|||
window.arguments.length >= 1 &&
|
||||
window.arguments[0];
|
||||
|
||||
if (!args || !args.doc) {
|
||||
gWindow = window.opener.gBrowser.selectedBrowser.contentWindowAsCPOW;
|
||||
gDocument = gWindow.document;
|
||||
}
|
||||
|
||||
// init media view
|
||||
var imageTree = document.getElementById("imagetree");
|
||||
imageTree.view = gImageView;
|
||||
|
@ -357,22 +350,52 @@ function onLoadPageInfo()
|
|||
.notifyObservers(window, "page-info-dialog-loaded", null);
|
||||
}
|
||||
|
||||
function loadPageInfo()
|
||||
function loadPageInfo(frameOuterWindowID)
|
||||
{
|
||||
var titleFormat = gWindow != gWindow.top ? "pageInfo.frame.title"
|
||||
: "pageInfo.page.title";
|
||||
document.title = gBundle.getFormattedString(titleFormat, [gDocument.location]);
|
||||
let mm = window.opener.gBrowser.selectedBrowser.messageManager;
|
||||
|
||||
document.getElementById("main-window").setAttribute("relatedUrl", gDocument.location);
|
||||
gStrings["application/rss+xml"] = gBundle.getString("feedRss");
|
||||
gStrings["application/atom+xml"] = gBundle.getString("feedAtom");
|
||||
gStrings["text/xml"] = gBundle.getString("feedXML");
|
||||
gStrings["application/xml"] = gBundle.getString("feedXML");
|
||||
gStrings["application/rdf+xml"] = gBundle.getString("feedXML");
|
||||
|
||||
// do the easy stuff first
|
||||
makeGeneralTab();
|
||||
// Look for pageInfoListener in content.js. Sends message to listener with arguments.
|
||||
mm.sendAsyncMessage("PageInfo:getData", {strings: gStrings,
|
||||
frameOuterWindowID: frameOuterWindowID});
|
||||
|
||||
// and then the hard stuff
|
||||
makeTabs(gDocument, gWindow);
|
||||
let pageInfoData = null;
|
||||
|
||||
initFeedTab();
|
||||
onLoadPermission();
|
||||
// Get initial pageInfoData needed to display the general, feeds, permission and security tabs.
|
||||
mm.addMessageListener("PageInfo:data", function onmessage(message) {
|
||||
mm.removeMessageListener("PageInfo:data", onmessage);
|
||||
pageInfoData = message.data;
|
||||
let docInfo = pageInfoData.docInfo;
|
||||
let windowInfo = pageInfoData.windowInfo;
|
||||
let uri = makeURI(docInfo.documentURIObject.spec,
|
||||
docInfo.documentURIObject.originCharset);
|
||||
gDocInfo = docInfo;
|
||||
|
||||
var titleFormat = windowInfo.isTopWindow ? "pageInfo.frame.title"
|
||||
: "pageInfo.page.title";
|
||||
document.title = gBundle.getFormattedString(titleFormat, [docInfo.location]);
|
||||
|
||||
document.getElementById("main-window").setAttribute("relatedUrl", docInfo.location);
|
||||
|
||||
makeGeneralTab(pageInfoData.metaViewRows, docInfo);
|
||||
initFeedTab(pageInfoData.feeds);
|
||||
onLoadPermission(uri);
|
||||
securityOnLoad(uri, windowInfo);
|
||||
});
|
||||
|
||||
// Get the media elements from content script to setup the media tab.
|
||||
mm.addMessageListener("PageInfo:mediaData", function onmessage(message){
|
||||
mm.removeMessageListener("PageInfo:mediaData", onmessage);
|
||||
makeMediaTab(message.data.imageViewRows);
|
||||
|
||||
// Loop through onFinished and execute the functions on it.
|
||||
onFinished.forEach(function(func) { func(pageInfoData); });
|
||||
});
|
||||
|
||||
/* Call registered overlay init functions */
|
||||
onLoadRegistry.forEach(function(func) { func(); });
|
||||
|
@ -443,15 +466,22 @@ function showTab(id)
|
|||
|
||||
function loadTab(args)
|
||||
{
|
||||
if (args && args.doc) {
|
||||
gDocument = args.doc;
|
||||
gWindow = gDocument.defaultView;
|
||||
// If the "View Image Info" context menu item was used, the related image
|
||||
// element is provided as an argument. This can't be a background image.
|
||||
let imageElement = args && args.imageElement;
|
||||
if (imageElement) {
|
||||
gImageElement = {currentSrc: imageElement.currentSrc,
|
||||
width: imageElement.width, height: imageElement.height,
|
||||
imageText: imageElement.title || imageElement.alt};
|
||||
}
|
||||
else {
|
||||
gImageElement = null;
|
||||
}
|
||||
|
||||
gImageElement = args && args.imageElement;
|
||||
let frameOuterWindowID = args && args.frameOuterWindowID;
|
||||
|
||||
/* Load the page info */
|
||||
loadPageInfo();
|
||||
loadPageInfo(frameOuterWindowID);
|
||||
|
||||
var initialTab = (args && args.initialTab) || "generalTab";
|
||||
var radioGroup = document.getElementById("viewGroup");
|
||||
|
@ -491,31 +521,29 @@ function openCacheEntry(key, cb)
|
|||
diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", nsICacheStorage.OPEN_READONLY, checkCacheListener);
|
||||
}
|
||||
|
||||
function makeGeneralTab()
|
||||
function makeGeneralTab(metaViewRows, docInfo)
|
||||
{
|
||||
var title = (gDocument.title) ? gBundle.getFormattedString("pageTitle", [gDocument.title]) : gBundle.getString("noPageTitle");
|
||||
var title = (docInfo.title) ? gBundle.getFormattedString("pageTitle", [docInfo.title]) : gBundle.getString("noPageTitle");
|
||||
document.getElementById("titletext").value = title;
|
||||
|
||||
var url = gDocument.location.toString();
|
||||
var url = docInfo.location;
|
||||
setItemValue("urltext", url);
|
||||
|
||||
var referrer = ("referrer" in gDocument && gDocument.referrer);
|
||||
var referrer = ("referrer" in docInfo && docInfo.referrer);
|
||||
setItemValue("refertext", referrer);
|
||||
|
||||
var mode = ("compatMode" in gDocument && gDocument.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
|
||||
var mode = ("compatMode" in docInfo && docInfo.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
|
||||
document.getElementById("modetext").value = gBundle.getString(mode);
|
||||
|
||||
// find out the mime type
|
||||
var mimeType = gDocument.contentType;
|
||||
var mimeType = docInfo.contentType;
|
||||
setItemValue("typetext", mimeType);
|
||||
|
||||
// get the document characterset
|
||||
var encoding = gDocument.characterSet;
|
||||
var encoding = docInfo.characterSet;
|
||||
document.getElementById("encodingtext").value = encoding;
|
||||
|
||||
// get the meta tags
|
||||
var metaNodes = gDocument.getElementsByTagName("meta");
|
||||
var length = metaNodes.length;
|
||||
let length = metaViewRows.length;
|
||||
|
||||
var metaGroup = document.getElementById("metaTags");
|
||||
if (!length)
|
||||
|
@ -529,15 +557,14 @@ function makeGeneralTab()
|
|||
var metaTree = document.getElementById("metatree");
|
||||
metaTree.view = gMetaView;
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
gMetaView.addRow([metaNodes[i].name || metaNodes[i].httpEquiv || metaNodes[i].getAttribute("property"),
|
||||
metaNodes[i].content]);
|
||||
// Add the metaViewRows onto the general tab's meta info tree.
|
||||
gMetaView.addRows(metaViewRows);
|
||||
|
||||
metaGroup.collapsed = false;
|
||||
}
|
||||
|
||||
// get the date of last modification
|
||||
var modifiedText = formatDate(gDocument.lastModified, gStrings.notSet);
|
||||
var modifiedText = formatDate(docInfo.lastModified, gStrings.notSet);
|
||||
document.getElementById("modifiedtext").value = modifiedText;
|
||||
|
||||
// get cache info
|
||||
|
@ -551,57 +578,16 @@ function makeGeneralTab()
|
|||
}
|
||||
setItemValue("sizetext", sizeText);
|
||||
});
|
||||
|
||||
securityOnLoad();
|
||||
}
|
||||
|
||||
//******** Generic Build-a-tab
|
||||
// Assumes the views are empty. Only called once to build the tabs, and
|
||||
// does so by farming the task off to another thread via setTimeout().
|
||||
// The actual work is done with a TreeWalker that calls doGrab() once for
|
||||
// each element node in the document.
|
||||
|
||||
var gFrameList = [ ];
|
||||
|
||||
function makeTabs(aDocument, aWindow)
|
||||
function makeMediaTab(imageViewRows)
|
||||
{
|
||||
goThroughFrames(aDocument, aWindow);
|
||||
processFrames();
|
||||
}
|
||||
|
||||
function goThroughFrames(aDocument, aWindow)
|
||||
{
|
||||
gFrameList.push(aDocument);
|
||||
if (aWindow && aWindow.frames.length > 0) {
|
||||
var num = aWindow.frames.length;
|
||||
for (var i = 0; i < num; i++)
|
||||
goThroughFrames(aWindow.frames[i].document, aWindow.frames[i]); // recurse through the frames
|
||||
// Call addImage passing in the image rows to add to the view on the Media Tab.
|
||||
for (let image of imageViewRows) {
|
||||
let [url, type, alt, elem, isBg] = image;
|
||||
addImage(url, type, alt, elem, isBg);
|
||||
}
|
||||
}
|
||||
|
||||
function processFrames()
|
||||
{
|
||||
if (gFrameList.length) {
|
||||
var doc = gFrameList[0];
|
||||
onProcessFrame.forEach(function(func) { func(doc); });
|
||||
var iterator = doc.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, grabAll);
|
||||
gFrameList.shift();
|
||||
setTimeout(doGrab, 10, iterator);
|
||||
onFinished.push(selectImage);
|
||||
}
|
||||
else
|
||||
onFinished.forEach(function(func) { func(); });
|
||||
}
|
||||
|
||||
function doGrab(iterator)
|
||||
{
|
||||
for (var i = 0; i < 500; ++i)
|
||||
if (!iterator.nextNode()) {
|
||||
processFrames();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(doGrab, 10, iterator);
|
||||
selectImage();
|
||||
}
|
||||
|
||||
function addImage(url, type, alt, elem, isBg)
|
||||
|
@ -639,80 +625,19 @@ function addImage(url, type, alt, elem, isBg)
|
|||
else {
|
||||
var i = gImageHash[url][type][alt];
|
||||
gImageView.data[i][COL_IMAGE_COUNT]++;
|
||||
if (elem == gImageElement)
|
||||
// The same image can occur several times on the page at different sizes.
|
||||
// If the "View Image Info" context menu item was used, ensure we select
|
||||
// the correct element.
|
||||
if (!gImageView.data[i][COL_IMAGE_BG] &&
|
||||
gImageElement && url == gImageElement.currentSrc &&
|
||||
gImageElement.width == elem.width &&
|
||||
gImageElement.height == elem.height &&
|
||||
gImageElement.imageText == elem.imageText) {
|
||||
gImageView.data[i][COL_IMAGE_NODE] = elem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function grabAll(elem)
|
||||
{
|
||||
// check for images defined in CSS (e.g. background, borders), any node may have multiple
|
||||
var computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, "");
|
||||
|
||||
if (computedStyle) {
|
||||
var addImgFunc = function (label, val) {
|
||||
if (val.primitiveType == CSSPrimitiveValue.CSS_URI) {
|
||||
addImage(val.getStringValue(), label, gStrings.notSet, elem, true);
|
||||
}
|
||||
else if (val.primitiveType == CSSPrimitiveValue.CSS_STRING) {
|
||||
// This is for -moz-image-rect.
|
||||
// TODO: Reimplement once bug 714757 is fixed
|
||||
var strVal = val.getStringValue();
|
||||
if (strVal.search(/^.*url\(\"?/) > -1) {
|
||||
url = strVal.replace(/^.*url\(\"?/,"").replace(/\"?\).*$/,"");
|
||||
addImage(url, label, gStrings.notSet, elem, true);
|
||||
}
|
||||
}
|
||||
else if (val.cssValueType == CSSValue.CSS_VALUE_LIST) {
|
||||
// recursively resolve multiple nested CSS value lists
|
||||
for (var i = 0; i < val.length; i++)
|
||||
addImgFunc(label, val.item(i));
|
||||
}
|
||||
};
|
||||
|
||||
addImgFunc(gStrings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image"));
|
||||
addImgFunc(gStrings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source"));
|
||||
addImgFunc(gStrings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image"));
|
||||
addImgFunc(gStrings.mediaCursor, computedStyle.getPropertyCSSValue("cursor"));
|
||||
}
|
||||
|
||||
// one swi^H^H^Hif-else to rule them all
|
||||
if (elem instanceof HTMLImageElement)
|
||||
addImage(elem.src, gStrings.mediaImg,
|
||||
(elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
|
||||
else if (elem instanceof SVGImageElement) {
|
||||
try {
|
||||
// Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
|
||||
// or the URI formed from the baseURI and the URL is not a valid URI
|
||||
var href = makeURLAbsolute(elem.baseURI, elem.href.baseVal);
|
||||
addImage(href, gStrings.mediaImg, "", elem, false);
|
||||
} catch (e) { }
|
||||
}
|
||||
else if (elem instanceof HTMLVideoElement) {
|
||||
addImage(elem.currentSrc, gStrings.mediaVideo, "", elem, false);
|
||||
}
|
||||
else if (elem instanceof HTMLAudioElement) {
|
||||
addImage(elem.currentSrc, gStrings.mediaAudio, "", elem, false);
|
||||
}
|
||||
else if (elem instanceof HTMLLinkElement) {
|
||||
if (elem.rel && /\bicon\b/i.test(elem.rel))
|
||||
addImage(elem.href, gStrings.mediaLink, "", elem, false);
|
||||
}
|
||||
else if (elem instanceof HTMLInputElement || elem instanceof HTMLButtonElement) {
|
||||
if (elem.type.toLowerCase() == "image")
|
||||
addImage(elem.src, gStrings.mediaInput,
|
||||
(elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
|
||||
}
|
||||
else if (elem instanceof HTMLObjectElement)
|
||||
addImage(elem.data, gStrings.mediaObject, getValueText(elem), elem, false);
|
||||
else if (elem instanceof HTMLEmbedElement)
|
||||
addImage(elem.src, gStrings.mediaEmbed, "", elem, false);
|
||||
|
||||
onProcessElement.forEach(function(func) { func(elem); });
|
||||
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
}
|
||||
|
||||
//******** Link Stuff
|
||||
function openURL(target)
|
||||
{
|
||||
|
@ -814,14 +739,15 @@ function saveMedia()
|
|||
else if (item instanceof HTMLAudioElement)
|
||||
titleKey = "SaveAudioTitle";
|
||||
|
||||
saveURL(url, null, titleKey, false, false, makeURI(item.baseURI), gDocument);
|
||||
saveURL(url, null, titleKey, false, false, makeURI(item.baseURI),
|
||||
null, gDocInfo.isContentWindowPrivate);
|
||||
}
|
||||
} else {
|
||||
selectSaveFolder(function(aDirectory) {
|
||||
if (aDirectory) {
|
||||
var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
|
||||
internalSave(aURIString, null, null, null, null, false, "SaveImageTitle",
|
||||
aChosenData, aBaseURI, gDocument);
|
||||
aChosenData, aBaseURI, null, gDocInfo.isContentWindowPrivate);
|
||||
};
|
||||
|
||||
for (var i = 0; i < rowArray.length; i++) {
|
||||
|
@ -893,6 +819,7 @@ function onImageSelect()
|
|||
}
|
||||
}
|
||||
|
||||
// Makes the media preview (image, video, etc) for the selected row on the media tab.
|
||||
function makePreview(row)
|
||||
{
|
||||
var imageTree = document.getElementById("imagetree");
|
||||
|
@ -902,18 +829,7 @@ function makePreview(row)
|
|||
var isAudio = false;
|
||||
|
||||
setItemValue("imageurltext", url);
|
||||
|
||||
var imageText;
|
||||
if (!isBG &&
|
||||
!(item instanceof SVGImageElement) &&
|
||||
!(gDocument instanceof ImageDocument)) {
|
||||
imageText = item.title || item.alt;
|
||||
|
||||
if (!imageText && !(item instanceof HTMLImageElement))
|
||||
imageText = getValueText(item);
|
||||
}
|
||||
setItemValue("imagetext", imageText);
|
||||
|
||||
setItemValue("imagetext", item.imageText);
|
||||
setItemValue("imagelongdesctext", item.longDesc);
|
||||
|
||||
// get cache info
|
||||
|
@ -931,32 +847,8 @@ function makePreview(row)
|
|||
sizeText = gBundle.getString("mediaUnknownNotCached");
|
||||
setItemValue("imagesizetext", sizeText);
|
||||
|
||||
var mimeType;
|
||||
var numFrames = 1;
|
||||
if (item instanceof HTMLObjectElement ||
|
||||
item instanceof HTMLEmbedElement ||
|
||||
item instanceof HTMLLinkElement)
|
||||
mimeType = item.type;
|
||||
|
||||
if (!mimeType && !isBG && item instanceof nsIImageLoadingContent) {
|
||||
var imageRequest = item.getRequest(nsIImageLoadingContent.CURRENT_REQUEST);
|
||||
if (imageRequest) {
|
||||
mimeType = imageRequest.mimeType;
|
||||
var image = imageRequest.image;
|
||||
if (image)
|
||||
numFrames = image.numFrames;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mimeType)
|
||||
mimeType = getContentTypeFromHeaders(cacheEntry);
|
||||
|
||||
// if we have a data url, get the MIME type from the url
|
||||
if (!mimeType && url.startsWith("data:")) {
|
||||
let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url);
|
||||
if (dataMimeType)
|
||||
mimeType = dataMimeType[1].toLowerCase();
|
||||
}
|
||||
var mimeType = item.mimeType || this.getContentTypeFromHeaders(cacheEntry);
|
||||
var numFrames = item.numFrames;
|
||||
|
||||
var imageType;
|
||||
if (mimeType) {
|
||||
|
@ -991,10 +883,10 @@ function makePreview(row)
|
|||
var physWidth = 0, physHeight = 0;
|
||||
var width = 0, height = 0;
|
||||
|
||||
if ((item instanceof HTMLLinkElement || item instanceof HTMLInputElement ||
|
||||
item instanceof HTMLImageElement ||
|
||||
item instanceof SVGImageElement ||
|
||||
(item instanceof HTMLObjectElement && mimeType && mimeType.startsWith("image/")) || isBG) && isProtocolAllowed) {
|
||||
if ((item.HTMLLinkElement || item.HTMLInputElement ||
|
||||
item.HTMLImageElement || item.SVGImageElement ||
|
||||
(item.HTMLObjectElement && mimeType && mimeType.startsWith("image/")) ||
|
||||
isBG) && isProtocolAllowed) {
|
||||
newImage.setAttribute("src", url);
|
||||
physWidth = newImage.width || 0;
|
||||
physHeight = newImage.height || 0;
|
||||
|
@ -1013,9 +905,9 @@ function makePreview(row)
|
|||
newImage.height = newImage.naturalHeight;
|
||||
}
|
||||
|
||||
if (item instanceof SVGImageElement) {
|
||||
newImage.width = item.width.baseVal.value;
|
||||
newImage.height = item.height.baseVal.value;
|
||||
if (item.SVGImageElement) {
|
||||
newImage.width = item.SVGImageElementWidth;
|
||||
newImage.height = item.SVGImageElementHeight;
|
||||
}
|
||||
|
||||
width = newImage.width;
|
||||
|
@ -1024,7 +916,7 @@ function makePreview(row)
|
|||
document.getElementById("theimagecontainer").collapsed = false
|
||||
document.getElementById("brokenimagecontainer").collapsed = true;
|
||||
}
|
||||
else if (item instanceof HTMLVideoElement && isProtocolAllowed) {
|
||||
else if (item.HTMLVideoElement && isProtocolAllowed) {
|
||||
newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
|
||||
newImage.id = "thepreviewimage";
|
||||
newImage.src = url;
|
||||
|
@ -1035,7 +927,7 @@ function makePreview(row)
|
|||
document.getElementById("theimagecontainer").collapsed = false;
|
||||
document.getElementById("brokenimagecontainer").collapsed = true;
|
||||
}
|
||||
else if (item instanceof HTMLAudioElement && isProtocolAllowed) {
|
||||
else if (item.HTMLAudioElement && isProtocolAllowed) {
|
||||
newImage = new Audio;
|
||||
newImage.id = "thepreviewimage";
|
||||
newImage.src = url;
|
||||
|
@ -1073,8 +965,6 @@ function makePreview(row)
|
|||
|
||||
imageContainer.removeChild(oldImage);
|
||||
imageContainer.appendChild(newImage);
|
||||
|
||||
onImagePreviewShown.forEach(function(func) { func(); });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1130,69 +1020,9 @@ function getContentTypeFromHeaders(cacheEntryDescriptor)
|
|||
if (!cacheEntryDescriptor)
|
||||
return null;
|
||||
|
||||
return (/^Content-Type:\s*(.*?)\s*(?:\;|$)/mi
|
||||
.exec(cacheEntryDescriptor.getMetaDataElement("response-head")))[1];
|
||||
}
|
||||
|
||||
//******** Other Misc Stuff
|
||||
// Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
|
||||
// parse a node to extract the contents of the node
|
||||
function getValueText(node)
|
||||
{
|
||||
var valueText = "";
|
||||
|
||||
// form input elements don't generally contain information that is useful to our callers, so return nothing
|
||||
if (node instanceof HTMLInputElement ||
|
||||
node instanceof HTMLSelectElement ||
|
||||
node instanceof HTMLTextAreaElement)
|
||||
return valueText;
|
||||
|
||||
// otherwise recurse for each child
|
||||
var length = node.childNodes.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var childNode = node.childNodes[i];
|
||||
var nodeType = childNode.nodeType;
|
||||
|
||||
// text nodes are where the goods are
|
||||
if (nodeType == Node.TEXT_NODE)
|
||||
valueText += " " + childNode.nodeValue;
|
||||
// and elements can have more text inside them
|
||||
else if (nodeType == Node.ELEMENT_NODE) {
|
||||
// images are special, we want to capture the alt text as if the image weren't there
|
||||
if (childNode instanceof HTMLImageElement)
|
||||
valueText += " " + getAltText(childNode);
|
||||
else
|
||||
valueText += " " + getValueText(childNode);
|
||||
}
|
||||
}
|
||||
|
||||
return stripWS(valueText);
|
||||
}
|
||||
|
||||
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
|
||||
// traverse the tree in search of an img or area element and grab its alt tag
|
||||
function getAltText(node)
|
||||
{
|
||||
var altText = "";
|
||||
|
||||
if (node.alt)
|
||||
return node.alt;
|
||||
var length = node.childNodes.length;
|
||||
for (var i = 0; i < length; i++)
|
||||
if ((altText = getAltText(node.childNodes[i]) != undefined)) // stupid js warning...
|
||||
return altText;
|
||||
return "";
|
||||
}
|
||||
|
||||
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
|
||||
// strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space
|
||||
function stripWS(text)
|
||||
{
|
||||
var middleRE = /\s+/g;
|
||||
var endRE = /(^\s+)|(\s+$)/g;
|
||||
|
||||
text = text.replace(middleRE, " ");
|
||||
return text.replace(endRE, "");
|
||||
let headers = cacheEntryDescriptor.getMetaDataElement("response-head");
|
||||
let type = /^Content-Type:\s*(.*?)\s*(?:\;|$)/mi.exec(headers);
|
||||
return type && type[1];
|
||||
}
|
||||
|
||||
function setItemValue(id, value)
|
||||
|
@ -1281,8 +1111,13 @@ function selectImage()
|
|||
|
||||
var tree = document.getElementById("imagetree");
|
||||
for (var i = 0; i < tree.view.rowCount; i++) {
|
||||
if (gImageElement == gImageView.data[i][COL_IMAGE_NODE] &&
|
||||
!gImageView.data[i][COL_IMAGE_BG]) {
|
||||
// If the image row element is the image selected from the "View Image Info" context menu item.
|
||||
let image = gImageView.data[i][COL_IMAGE_NODE];
|
||||
if (!gImageView.data[i][COL_IMAGE_BG] &&
|
||||
gImageElement.currentSrc == gImageView.data[i][COL_IMAGE_ADDRESS] &&
|
||||
gImageElement.width == image.width &&
|
||||
gImageElement.height == image.height &&
|
||||
gImageElement.imageText == image.imageText) {
|
||||
tree.view.selection.select(i);
|
||||
tree.treeBoxObject.ensureRowIsVisible(i);
|
||||
tree.focus();
|
||||
|
|
|
@ -28,9 +28,8 @@ var permissionObserver = {
|
|||
}
|
||||
};
|
||||
|
||||
function onLoadPermission()
|
||||
function onLoadPermission(uri)
|
||||
{
|
||||
var uri = BrowserUtils.makeURIFromCPOW(gDocument.documentURIObject);
|
||||
var permTab = document.getElementById("permTab");
|
||||
if (SitePermissions.isSupportedURI(uri)) {
|
||||
gPermURI = uri;
|
||||
|
|
|
@ -9,6 +9,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
|
|||
"resource://gre/modules/LoginHelper.jsm");
|
||||
|
||||
var security = {
|
||||
init: function(uri, windowInfo) {
|
||||
this.uri = uri;
|
||||
this.windowInfo = windowInfo;
|
||||
},
|
||||
|
||||
// Display the server certificate (static)
|
||||
viewCert : function () {
|
||||
var cert = security._cert;
|
||||
|
@ -24,14 +29,10 @@ var security = {
|
|||
|
||||
// We don't have separate info for a frame, return null until further notice
|
||||
// (see bug 138479)
|
||||
if (gWindow != gWindow.top)
|
||||
if (!this.windowInfo.isTopWindow)
|
||||
return null;
|
||||
|
||||
var hName = null;
|
||||
try {
|
||||
hName = gWindow.location.host;
|
||||
}
|
||||
catch (exception) { }
|
||||
var hostName = this.windowInfo.hostName;
|
||||
|
||||
var ui = security._getSecurityUI();
|
||||
if (!ui)
|
||||
|
@ -56,7 +57,7 @@ var security = {
|
|||
this.mapIssuerOrganization(cert.issuerOrganization) || cert.issuerName;
|
||||
|
||||
var retval = {
|
||||
hostName : hName,
|
||||
hostName : hostName,
|
||||
cAName : issuerName,
|
||||
encryptionAlgorithm : undefined,
|
||||
encryptionStrength : undefined,
|
||||
|
@ -64,8 +65,7 @@ var security = {
|
|||
isBroken : isBroken,
|
||||
isMixed : isMixed,
|
||||
isEV : isEV,
|
||||
cert : cert,
|
||||
fullLocation : gWindow.location
|
||||
cert : cert
|
||||
};
|
||||
|
||||
var version;
|
||||
|
@ -95,7 +95,7 @@ var security = {
|
|||
return retval;
|
||||
} else {
|
||||
return {
|
||||
hostName : hName,
|
||||
hostName : hostName,
|
||||
cAName : "",
|
||||
encryptionAlgorithm : "",
|
||||
encryptionStrength : 0,
|
||||
|
@ -103,8 +103,8 @@ var security = {
|
|||
isBroken : isBroken,
|
||||
isMixed : isMixed,
|
||||
isEV : isEV,
|
||||
cert : null,
|
||||
fullLocation : gWindow.location
|
||||
cert : null
|
||||
|
||||
};
|
||||
}
|
||||
},
|
||||
|
@ -140,13 +140,12 @@ var security = {
|
|||
getService(Components.interfaces.nsIEffectiveTLDService);
|
||||
|
||||
var eTLD;
|
||||
var uri = BrowserUtils.makeURIFromCPOW(gDocument.documentURIObject);
|
||||
try {
|
||||
eTLD = eTLDService.getBaseDomain(uri);
|
||||
eTLD = eTLDService.getBaseDomain(this.uri);
|
||||
}
|
||||
catch (e) {
|
||||
// getBaseDomain will fail if the host is an IP address or is empty
|
||||
eTLD = uri.asciiHost;
|
||||
eTLD = this.uri.asciiHost;
|
||||
}
|
||||
|
||||
if (win) {
|
||||
|
@ -168,7 +167,9 @@ var security = {
|
|||
_cert : null
|
||||
};
|
||||
|
||||
function securityOnLoad() {
|
||||
function securityOnLoad(uri, windowInfo) {
|
||||
security.init(uri, windowInfo);
|
||||
|
||||
var info = security._getSecurityInfo();
|
||||
if (!info) {
|
||||
document.getElementById("securityTab").hidden = true;
|
||||
|
@ -226,7 +227,6 @@ function securityOnLoad() {
|
|||
var yesStr = pageInfoBundle.getString("yes");
|
||||
var noStr = pageInfoBundle.getString("no");
|
||||
|
||||
var uri = BrowserUtils.makeURIFromCPOW(gDocument.documentURIObject);
|
||||
setText("security-privacy-cookies-value",
|
||||
hostHasCookies(uri) ? yesStr : noStr);
|
||||
setText("security-privacy-passwords-value",
|
||||
|
|
|
@ -14,7 +14,7 @@ function test() {
|
|||
|
||||
pageInfo.addEventListener("load", function () {
|
||||
pageInfo.removeEventListener("load", arguments.callee, true);
|
||||
pageInfo.onImagePreviewShown.push(function () {
|
||||
pageInfo.onFinished.push(function () {
|
||||
executeSoon(function () {
|
||||
var pageInfoImg = pageInfo.document.getElementById("thepreviewimage");
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ let Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard
|
|||
let HasFindClipboard = Clipboard.supportsFindClipboard();
|
||||
|
||||
function addTabWithText(aText, aCallback) {
|
||||
let newTab = gBrowser.addTab("data:text/html,<h1 id='h1'>" + aText + "</h1>");
|
||||
let newTab = gBrowser.addTab("data:text/html;charset=utf-8,<h1 id='h1'>" +
|
||||
aText + "</h1>");
|
||||
tabs.push(newTab);
|
||||
gBrowser.selectedTab = newTab;
|
||||
}
|
||||
|
@ -78,6 +79,12 @@ function continueTests1() {
|
|||
|
||||
function continueTests2() {
|
||||
gBrowser.removeEventListener("DOMContentLoaded", continueTests2, true);
|
||||
waitForCondition(() => !gFindBar.getElement("highlight").checked,
|
||||
continueTests3,
|
||||
"Highlight never reset!");
|
||||
}
|
||||
|
||||
function continueTests3() {
|
||||
ok(!gFindBar.getElement("highlight").checked, "Highlight button reset!");
|
||||
gFindBar.close();
|
||||
ok(gFindBar.hidden, "First tab doesn't show find bar!");
|
||||
|
|
|
@ -1,138 +1,140 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
/*
|
||||
* Test the identity mode UI for a variety of page types
|
||||
*/
|
||||
|
||||
const DUMMY = "browser/browser/base/content/test/general/dummy_page.html";
|
||||
|
||||
function loadNewTab(aURL, aCallback) {
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.loadURI(aURL);
|
||||
|
||||
gBrowser.selectedBrowser.addEventListener("load", function() {
|
||||
if (gBrowser.selectedBrowser.currentURI.spec != aURL)
|
||||
return;
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
|
||||
aCallback(gBrowser.selectedTab);
|
||||
}, true);
|
||||
function loadNewTab(url) {
|
||||
return BrowserTestUtils.openNewForegroundTab(gBrowser, url);
|
||||
}
|
||||
|
||||
function getIdentityMode() {
|
||||
return document.getElementById("identity-box").className;
|
||||
}
|
||||
|
||||
var TESTS = [
|
||||
function test_webpage() {
|
||||
// This test is slow on Linux debug e10s
|
||||
requestLongerTimeout(2);
|
||||
|
||||
add_task(function* test_webpage() {
|
||||
let oldTab = gBrowser.selectedTab;
|
||||
|
||||
loadNewTab("http://example.com/" + DUMMY, function(aNewTab) {
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
let newTab = yield loadNewTab("http://example.com/" + DUMMY);
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = aNewTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
gBrowser.selectedTab = newTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.removeTab(aNewTab);
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
||||
runNextTest();
|
||||
});
|
||||
},
|
||||
|
||||
function test_blank() {
|
||||
add_task(function* test_blank() {
|
||||
let oldTab = gBrowser.selectedTab;
|
||||
|
||||
loadNewTab("about:blank", function(aNewTab) {
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
let newTab = yield loadNewTab("about:blank");
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = aNewTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
gBrowser.selectedTab = newTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.removeTab(aNewTab);
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
||||
runNextTest();
|
||||
});
|
||||
},
|
||||
|
||||
function test_chrome() {
|
||||
add_task(function* test_chrome() {
|
||||
let oldTab = gBrowser.selectedTab;
|
||||
|
||||
// Since users aren't likely to type in full chrome URLs, we won't show
|
||||
// the positive security indicator on it, but we will show it on about:addons.
|
||||
loadNewTab("chrome://mozapps/content/extensions/extensions.xul", function(aNewTab) {
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
let newTab = yield loadNewTab("chrome://mozapps/content/extensions/extensions.xul");
|
||||
is(getIdentityMode(), "fileURI", "Identity should be file");
|
||||
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = aNewTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
gBrowser.selectedTab = newTab;
|
||||
is(getIdentityMode(), "fileURI", "Identity should be file");
|
||||
|
||||
gBrowser.removeTab(aNewTab);
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
||||
runNextTest();
|
||||
});
|
||||
},
|
||||
|
||||
function test_https() {
|
||||
add_task(function* test_https() {
|
||||
let oldTab = gBrowser.selectedTab;
|
||||
|
||||
loadNewTab("https://example.com/" + DUMMY, function(aNewTab) {
|
||||
is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
|
||||
let newTab = yield loadNewTab("https://example.com/" + DUMMY);
|
||||
is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
|
||||
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = aNewTab;
|
||||
is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
|
||||
gBrowser.selectedTab = newTab;
|
||||
is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
|
||||
|
||||
gBrowser.removeTab(aNewTab);
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
||||
runNextTest();
|
||||
});
|
||||
},
|
||||
|
||||
function test_addons() {
|
||||
add_task(function* test_addons() {
|
||||
let oldTab = gBrowser.selectedTab;
|
||||
|
||||
loadNewTab("about:addons", function(aNewTab) {
|
||||
is(getIdentityMode(), "chromeUI", "Identity should be chrome");
|
||||
let newTab = yield loadNewTab("about:addons");
|
||||
is(getIdentityMode(), "chromeUI", "Identity should be chrome");
|
||||
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = aNewTab;
|
||||
is(getIdentityMode(), "chromeUI", "Identity should be chrome");
|
||||
gBrowser.selectedTab = newTab;
|
||||
is(getIdentityMode(), "chromeUI", "Identity should be chrome");
|
||||
|
||||
gBrowser.removeTab(aNewTab);
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
||||
runNextTest();
|
||||
});
|
||||
}
|
||||
];
|
||||
add_task(function* test_file() {
|
||||
let oldTab = gBrowser.selectedTab;
|
||||
let fileURI = getTestFilePath("");
|
||||
|
||||
var gTestStart = null;
|
||||
let newTab = yield loadNewTab(fileURI);
|
||||
is(getIdentityMode(), "fileURI", "Identity should be file");
|
||||
|
||||
function runNextTest() {
|
||||
if (gTestStart)
|
||||
info("Test part took " + (Date.now() - gTestStart) + "ms");
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
if (TESTS.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
gBrowser.selectedTab = newTab;
|
||||
is(getIdentityMode(), "fileURI", "Identity should be file");
|
||||
|
||||
info("Running " + TESTS[0].name);
|
||||
gTestStart = Date.now();
|
||||
TESTS.shift()();
|
||||
};
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
add_task(function test_resource_uri() {
|
||||
let oldTab = gBrowser.selectedTab;
|
||||
let dataURI = "resource://gre/modules/Services.jsm"
|
||||
|
||||
runNextTest();
|
||||
}
|
||||
let newTab = yield loadNewTab(dataURI);
|
||||
|
||||
is(getIdentityMode(), "fileURI", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = newTab;
|
||||
is(getIdentityMode(), "fileURI", "Identity should be unknown");
|
||||
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
||||
add_task(function test_data_uri() {
|
||||
let oldTab = gBrowser.selectedTab;
|
||||
let dataURI = "data:text/html,hi"
|
||||
|
||||
let newTab = yield loadNewTab(dataURI);
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = oldTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.selectedTab = newTab;
|
||||
is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
|
||||
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
|
|
@ -102,7 +102,7 @@ add_task(function* rightLeftKeys() {
|
|||
// trigger suggestions again and cycle through them by pressing Down until
|
||||
// nothing is selected again.
|
||||
state = yield msg("key", "VK_RIGHT");
|
||||
checkState(state, "xfoo", [], 0);
|
||||
checkState(state, "xfoo", [], -1);
|
||||
|
||||
state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
|
||||
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
|
||||
|
@ -125,20 +125,202 @@ add_task(function* rightLeftKeys() {
|
|||
yield msg("reset");
|
||||
});
|
||||
|
||||
add_task(function* tabKey() {
|
||||
yield setUp();
|
||||
yield msg("key", { key: "x", waitForSuggestions: true });
|
||||
|
||||
let state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "x", ["xfoo", "xbar"], 2);
|
||||
|
||||
state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "x", ["xfoo", "xbar"], 3);
|
||||
|
||||
state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }});
|
||||
checkState(state, "x", ["xfoo", "xbar"], 2);
|
||||
|
||||
state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }});
|
||||
checkState(state, "x", [], -1);
|
||||
|
||||
yield setUp();
|
||||
|
||||
yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
|
||||
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
state = yield msg("key", "VK_TAB");
|
||||
}
|
||||
checkState(state, "x", [], -1);
|
||||
|
||||
yield setUp();
|
||||
|
||||
yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
|
||||
state = yield msg("key", "VK_DOWN");
|
||||
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
|
||||
|
||||
state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "xfoo", ["xfoo", "xbar"], 0, 0);
|
||||
|
||||
state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "xfoo", ["xfoo", "xbar"], 0, 1);
|
||||
|
||||
state = yield msg("key", "VK_DOWN");
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
|
||||
|
||||
state = yield msg("key", "VK_DOWN");
|
||||
checkState(state, "x", ["xfoo", "xbar"], 2);
|
||||
|
||||
state = yield msg("key", "VK_UP");
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1);
|
||||
|
||||
state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
|
||||
|
||||
state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
|
||||
|
||||
state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "xbar", [], -1);
|
||||
|
||||
yield msg("reset");
|
||||
});
|
||||
|
||||
add_task(function* cycleSuggestions() {
|
||||
yield setUp();
|
||||
yield msg("key", { key: "x", waitForSuggestions: true });
|
||||
|
||||
let cycle = Task.async(function* (aSelectedButtonIndex) {
|
||||
let modifiers = {
|
||||
shiftKey: true,
|
||||
accelKey: true,
|
||||
};
|
||||
|
||||
let state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
|
||||
checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
|
||||
|
||||
state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
|
||||
|
||||
state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
|
||||
checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
|
||||
|
||||
state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
|
||||
checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
|
||||
|
||||
state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
|
||||
checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
|
||||
|
||||
state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
|
||||
|
||||
state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
|
||||
checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
|
||||
|
||||
state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
|
||||
checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
|
||||
});
|
||||
|
||||
yield cycle();
|
||||
|
||||
// Repeat with a one-off selected.
|
||||
let state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "x", ["xfoo", "xbar"], 2);
|
||||
yield cycle(0);
|
||||
|
||||
// Repeat with the settings button selected.
|
||||
state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "x", ["xfoo", "xbar"], 3);
|
||||
yield cycle(1);
|
||||
|
||||
yield msg("reset");
|
||||
});
|
||||
|
||||
add_task(function* cycleOneOffs() {
|
||||
yield setUp();
|
||||
yield msg("key", { key: "x", waitForSuggestions: true });
|
||||
|
||||
yield msg("addDuplicateOneOff");
|
||||
|
||||
let state = yield msg("key", "VK_DOWN");
|
||||
state = yield msg("key", "VK_DOWN");
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1);
|
||||
|
||||
let modifiers = {
|
||||
altKey: true,
|
||||
};
|
||||
|
||||
state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
|
||||
|
||||
state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
|
||||
|
||||
state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1);
|
||||
|
||||
state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
|
||||
|
||||
state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
|
||||
|
||||
state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1);
|
||||
|
||||
// If the settings button is selected, pressing alt+up/down should select the
|
||||
// last/first one-off respectively (and deselect the settings button).
|
||||
yield msg("key", "VK_TAB");
|
||||
yield msg("key", "VK_TAB");
|
||||
state = yield msg("key", "VK_TAB"); // Settings button selected.
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
|
||||
|
||||
state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
|
||||
|
||||
state = yield msg("key", "VK_TAB");
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
|
||||
|
||||
state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
|
||||
checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
|
||||
|
||||
yield msg("removeLastOneOff");
|
||||
yield msg("reset");
|
||||
});
|
||||
|
||||
add_task(function* mouse() {
|
||||
yield setUp();
|
||||
|
||||
let state = yield msg("key", { key: "x", waitForSuggestions: true });
|
||||
checkState(state, "x", ["xfoo", "xbar"], -1);
|
||||
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
state = yield msg("mousemove", i);
|
||||
checkState(state, "x", ["xfoo", "xbar"], i);
|
||||
}
|
||||
state = yield msg("mousemove", 0);
|
||||
checkState(state, "x", ["xfoo", "xbar"], 0);
|
||||
|
||||
state = yield msg("mousemove", 1);
|
||||
checkState(state, "x", ["xfoo", "xbar"], 1);
|
||||
|
||||
state = yield msg("mousemove", 2);
|
||||
checkState(state, "x", ["xfoo", "xbar"], 1, 0);
|
||||
|
||||
state = yield msg("mousemove", 3);
|
||||
checkState(state, "x", ["xfoo", "xbar"], 1, 1);
|
||||
|
||||
state = yield msg("mousemove", -1);
|
||||
checkState(state, "x", ["xfoo", "xbar"], 1);
|
||||
|
||||
yield msg("reset");
|
||||
yield setUp();
|
||||
|
||||
state = yield msg("key", { key: "x", waitForSuggestions: true });
|
||||
checkState(state, "x", ["xfoo", "xbar"], -1);
|
||||
|
||||
state = yield msg("mousemove", 0);
|
||||
checkState(state, "x", ["xfoo", "xbar"], 0);
|
||||
|
||||
state = yield msg("mousemove", 2);
|
||||
checkState(state, "x", ["xfoo", "xbar"], 0, 0);
|
||||
|
||||
state = yield msg("mousemove", -1);
|
||||
checkState(state, "x", ["xfoo", "xbar"], 0);
|
||||
|
||||
yield msg("reset");
|
||||
});
|
||||
|
||||
|
@ -197,6 +379,33 @@ add_task(function* formHistory() {
|
|||
yield msg("reset");
|
||||
});
|
||||
|
||||
add_task(function* cycleEngines() {
|
||||
yield setUp();
|
||||
yield msg("key", "VK_DOWN");
|
||||
|
||||
function promiseEngineChange(newEngineName) {
|
||||
let deferred = Promise.defer();
|
||||
Services.obs.addObserver(function resolver(subj, topic, data) {
|
||||
if (data != "engine-current") {
|
||||
return;
|
||||
}
|
||||
is(subj.name, newEngineName, "Engine cycled correctly");
|
||||
Services.obs.removeObserver(resolver, "browser-search-engine-modified");
|
||||
deferred.resolve();
|
||||
}, "browser-search-engine-modified", false);
|
||||
}
|
||||
|
||||
let p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME);
|
||||
yield msg("key", { key: "VK_DOWN", modifiers: { accelKey: true }});
|
||||
yield p;
|
||||
|
||||
p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME);
|
||||
yield msg("key", { key: "VK_UP", modifiers: { accelKey: true }});
|
||||
yield p;
|
||||
|
||||
yield msg("reset");
|
||||
});
|
||||
|
||||
add_task(function* search() {
|
||||
yield setUp();
|
||||
|
||||
|
@ -297,6 +506,42 @@ add_task(function* search() {
|
|||
yield promiseTab();
|
||||
yield setUp();
|
||||
|
||||
// Test selecting a suggestion, then clicking a one-off without deselecting the
|
||||
// suggestion.
|
||||
yield msg("key", { key: "x", waitForSuggestions: true });
|
||||
p = msg("waitForSearch");
|
||||
yield msg("mousemove", 1);
|
||||
yield msg("mousemove", 3);
|
||||
yield msg("click", { eltIdx: 3, modifiers: modifiers });
|
||||
mesg = yield p;
|
||||
eventData.searchString = "xfoo"
|
||||
eventData.selection = {
|
||||
index: 1,
|
||||
kind: "mouse",
|
||||
};
|
||||
SimpleTest.isDeeply(eventData, mesg, "Search event data");
|
||||
|
||||
yield promiseTab();
|
||||
yield setUp();
|
||||
|
||||
// Same as above, but with the keyboard.
|
||||
delete modifiers.button;
|
||||
yield msg("key", { key: "x", waitForSuggestions: true });
|
||||
p = msg("waitForSearch");
|
||||
yield msg("key", "VK_DOWN");
|
||||
yield msg("key", "VK_DOWN");
|
||||
yield msg("key", "VK_TAB");
|
||||
yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
|
||||
mesg = yield p;
|
||||
eventData.selection = {
|
||||
index: 1,
|
||||
kind: "key",
|
||||
};
|
||||
SimpleTest.isDeeply(eventData, mesg, "Search event data");
|
||||
|
||||
yield promiseTab();
|
||||
yield setUp();
|
||||
|
||||
// Test searching when using IME composition.
|
||||
let state = yield msg("startComposition", { data: "" });
|
||||
checkState(state, "", [], -1);
|
||||
|
@ -308,8 +553,10 @@ add_task(function* search() {
|
|||
p = msg("waitForSearch");
|
||||
yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
|
||||
mesg = yield p;
|
||||
eventData.searchString = "x"
|
||||
eventData.originalEvent = modifiers;
|
||||
eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
|
||||
delete eventData.selection;
|
||||
SimpleTest.isDeeply(eventData, mesg, "Search event data");
|
||||
|
||||
yield promiseTab();
|
||||
|
@ -428,7 +675,7 @@ function msg(type, data=null) {
|
|||
}
|
||||
|
||||
function checkState(actualState, expectedInputVal, expectedSuggestions,
|
||||
expectedSelectedIdx) {
|
||||
expectedSelectedIdx, expectedSelectedButtonIdx) {
|
||||
expectedSuggestions = expectedSuggestions.map(sugg => {
|
||||
return typeof(sugg) == "object" ? sugg : {
|
||||
str: sugg,
|
||||
|
@ -436,6 +683,10 @@ function checkState(actualState, expectedInputVal, expectedSuggestions,
|
|||
};
|
||||
});
|
||||
|
||||
if (expectedSelectedIdx == -1 && expectedSelectedButtonIdx != undefined) {
|
||||
expectedSelectedIdx = expectedSuggestions.length + expectedSelectedButtonIdx;
|
||||
}
|
||||
|
||||
let expectedState = {
|
||||
selectedIndex: expectedSelectedIdx,
|
||||
numSuggestions: expectedSuggestions.length,
|
||||
|
@ -448,6 +699,15 @@ function checkState(actualState, expectedInputVal, expectedSuggestions,
|
|||
inputValue: expectedInputVal,
|
||||
ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true",
|
||||
};
|
||||
if (expectedSelectedButtonIdx != undefined) {
|
||||
expectedState.selectedButtonIndex = expectedSelectedButtonIdx;
|
||||
}
|
||||
else if (expectedSelectedIdx < expectedSuggestions.length) {
|
||||
expectedState.selectedButtonIndex = -1;
|
||||
}
|
||||
else {
|
||||
expectedState.selectedButtonIndex = expectedSelectedIdx - expectedSuggestions.length;
|
||||
}
|
||||
|
||||
SimpleTest.isDeeply(actualState, expectedState, "State");
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ function test() {
|
|||
|
||||
function observer(win, topic, data) {
|
||||
Services.obs.removeObserver(observer, "page-info-dialog-loaded");
|
||||
handlePageInfo();
|
||||
pageInfo.onFinished.push(handlePageInfo);
|
||||
}
|
||||
|
||||
function handlePageInfo() {
|
||||
|
|
|
@ -22,6 +22,11 @@ function hidden(sel) {
|
|||
return display === "none";
|
||||
}
|
||||
|
||||
function identityPopupState() {
|
||||
let win = browser.ownerGlobal;
|
||||
return win.document.getElementById("identity-popup").state;
|
||||
}
|
||||
|
||||
function clickButton(sel) {
|
||||
let win = browser.ownerGlobal;
|
||||
let el = win.document.querySelector(sel);
|
||||
|
@ -85,6 +90,8 @@ add_task(function* testExceptionAddition() {
|
|||
info("Disable TP for the page (which reloads the page)");
|
||||
let tabReloadPromise = promiseTabLoadEvent(tab);
|
||||
clickButton("#tracking-action-unblock");
|
||||
is(identityPopupState(), "closed", "foobar");
|
||||
|
||||
yield tabReloadPromise;
|
||||
testTrackingPageUnblocked();
|
||||
|
||||
|
@ -115,6 +122,8 @@ add_task(function* testExceptionPersistence() {
|
|||
info("Disable TP for the page (which reloads the page)");
|
||||
let tabReloadPromise = promiseTabLoadEvent(tab);
|
||||
clickButton("#tracking-action-unblock");
|
||||
is(identityPopupState(), "closed", "foobar");
|
||||
|
||||
yield tabReloadPromise;
|
||||
testTrackingPageUnblocked();
|
||||
|
||||
|
|
|
@ -124,11 +124,27 @@ let messageHandlers = {
|
|||
ack("addInputValueToFormHistory");
|
||||
},
|
||||
|
||||
addDuplicateOneOff: function () {
|
||||
let btn = gController._oneOffButtons[gController._oneOffButtons.length - 1];
|
||||
let newBtn = btn.cloneNode(true);
|
||||
btn.parentNode.appendChild(newBtn);
|
||||
gController._oneOffButtons.push(newBtn);
|
||||
ack("addDuplicateOneOff");
|
||||
},
|
||||
|
||||
removeLastOneOff: function () {
|
||||
gController._oneOffButtons.pop().remove();
|
||||
ack("removeLastOneOff");
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
// Reset both the input and suggestions by select all + delete.
|
||||
// Reset both the input and suggestions by select all + delete. If there was
|
||||
// no text entered, this won't have any effect, so also escape to ensure the
|
||||
// suggestions table is closed.
|
||||
gController.input.focus();
|
||||
content.synthesizeKey("a", { accelKey: true });
|
||||
content.synthesizeKey("VK_DELETE", {});
|
||||
content.synthesizeKey("VK_ESCAPE", {});
|
||||
ack("reset");
|
||||
},
|
||||
};
|
||||
|
@ -165,6 +181,7 @@ function waitForContentSearchEvent(messageType, cb) {
|
|||
function currentState() {
|
||||
let state = {
|
||||
selectedIndex: gController.selectedIndex,
|
||||
selectedButtonIndex: gController.selectedButtonIndex,
|
||||
numSuggestions: gController._table.hidden ? 0 : gController.numSuggestions,
|
||||
suggestionAtIndex: [],
|
||||
isFormHistorySuggestionAtIndex: [],
|
||||
|
|
|
@ -34,7 +34,7 @@ function doOnOpenPageInfo(continuation) {
|
|||
|
||||
function pageInfoObserve(win, topic, data) {
|
||||
Services.obs.removeObserver(pageInfoObserve, "page-info-dialog-loaded");
|
||||
executeSoon(gNextTest);
|
||||
gPageInfo.onFinished.push(() => executeSoon(gNextTest));
|
||||
}
|
||||
|
||||
function finishTest() {
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
value="&identity.connectionSecure;"/>
|
||||
<label class="identity-popup-connection-not-secure identity-popup-text"
|
||||
value="&identity.connectionNotSecure;"/>
|
||||
<label class="identity-popup-connection-file-uri identity-popup-text"
|
||||
value="&identity.connectionFile;"/>
|
||||
<label class="identity-popup-connection-internal identity-popup-text"
|
||||
value="&identity.connectionInternal;"/>
|
||||
</vbox>
|
||||
|
@ -95,6 +97,8 @@
|
|||
value="&identity.connectionSecure;"/>
|
||||
<label class="identity-popup-connection-not-secure identity-popup-text"
|
||||
value="&identity.connectionNotSecure;"/>
|
||||
<label class="identity-popup-connection-file-uri identity-popup-text"
|
||||
value="&identity.connectionFile;"/>
|
||||
<label class="identity-popup-connection-internal identity-popup-text"
|
||||
value="&identity.connectionInternal;"/>
|
||||
</vbox>
|
||||
|
|
|
@ -121,12 +121,8 @@ function configureFxAccountIdentity() {
|
|||
storageManager.initialize(user);
|
||||
return new AccountState(storageManager);
|
||||
},
|
||||
getCertificate(data, keyPair, mustBeValidUntil) {
|
||||
this.cert = {
|
||||
validUntil: this.now() + 10000,
|
||||
cert: "certificate",
|
||||
};
|
||||
return Promise.resolve(this.cert.cert);
|
||||
_getAssertion(audience) {
|
||||
return Promise.resolve("assertion");
|
||||
},
|
||||
getCertificateSigned() {
|
||||
return Promise.resolve();
|
||||
|
|
|
@ -182,6 +182,10 @@ this.SessionStore = {
|
|||
return SessionStoreInternal.canRestoreLastSession;
|
||||
},
|
||||
|
||||
get crashedTabCount() {
|
||||
return SessionStoreInternal._crashedBrowsersCount;
|
||||
},
|
||||
|
||||
set canRestoreLastSession(val) {
|
||||
SessionStoreInternal.canRestoreLastSession = val;
|
||||
},
|
||||
|
@ -302,6 +306,10 @@ this.SessionStore = {
|
|||
return SessionStoreInternal.reviveCrashedTab(aTab);
|
||||
},
|
||||
|
||||
reviveAllCrashedTabs() {
|
||||
return SessionStoreInternal.reviveAllCrashedTabs();
|
||||
},
|
||||
|
||||
navigateAndRestore(tab, loadArguments, historyIndex) {
|
||||
return SessionStoreInternal.navigateAndRestore(tab, loadArguments, historyIndex);
|
||||
}
|
||||
|
@ -331,6 +339,9 @@ let SessionStoreInternal = {
|
|||
// they get restored).
|
||||
_crashedBrowsers: new WeakSet(),
|
||||
|
||||
// The number of crashed browsers.
|
||||
_crashedBrowsersCount: 0,
|
||||
|
||||
// A map (xul:browser -> nsIFrameLoader) that maps a browser to the last
|
||||
// associated frameLoader we heard about.
|
||||
_lastKnownFrameLoader: new WeakMap(),
|
||||
|
@ -1408,6 +1419,10 @@ let SessionStoreInternal = {
|
|||
if (!aNoNotification) {
|
||||
this.saveStateDelayed(aWindow);
|
||||
}
|
||||
|
||||
if (this._crashedBrowsers.has(browser.permanentKey)) {
|
||||
this._crashedBrowsersCount++;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1437,6 +1452,10 @@ let SessionStoreInternal = {
|
|||
if (!aNoNotification) {
|
||||
this.saveStateDelayed(aWindow);
|
||||
}
|
||||
|
||||
if (this._crashedBrowsers.has(browser.permanentKey)) {
|
||||
this._crashedBrowsersCount--;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1616,6 +1635,7 @@ let SessionStoreInternal = {
|
|||
*/
|
||||
onBrowserCrashed: function(aWindow, aBrowser) {
|
||||
this._crashedBrowsers.add(aBrowser.permanentKey);
|
||||
this._crashedBrowsersCount++;
|
||||
// If we never got around to restoring this tab, clear its state so
|
||||
// that we don't try restoring if the user switches to it before
|
||||
// reviving the crashed browser. This is throwing away the information
|
||||
|
@ -2171,6 +2191,23 @@ let SessionStoreInternal = {
|
|||
|
||||
let data = TabState.collect(aTab);
|
||||
this.restoreTab(aTab, data);
|
||||
|
||||
this._crashedBrowsersCount--;
|
||||
},
|
||||
|
||||
/**
|
||||
* Revive all crashed tabs and reset the crashed tabs count to 0.
|
||||
*/
|
||||
reviveAllCrashedTabs() {
|
||||
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
let window = windowsEnum.getNext();
|
||||
for (let tab of window.gBrowser.tabs) {
|
||||
this.reviveCrashedTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
this._crashedBrowsersCount = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -341,3 +341,49 @@ add_task(function test_close_tab_after_crash() {
|
|||
|
||||
is(gBrowser.tabs.length, 1, "Should have closed the tab");
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks that "restore all" button is only shown if more than one tab
|
||||
* has crashed.
|
||||
*/
|
||||
add_task(function* test_hide_restore_all_button() {
|
||||
let newTab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = newTab;
|
||||
let browser = newTab.linkedBrowser;
|
||||
ok(browser.isRemoteBrowser, "Should be a remote browser");
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
browser.loadURI(PAGE_1);
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
yield TabStateFlusher.flush(browser);
|
||||
|
||||
// Crash the tab
|
||||
yield crashBrowser(browser);
|
||||
|
||||
let doc = browser.contentDocument;
|
||||
let restoreAllButton = doc.getElementById("restoreAll");
|
||||
let restoreOneButton = doc.getElementById("restoreTab");
|
||||
|
||||
is(restoreAllButton.getAttribute("hidden"), "true", "Restore All button should be hidden");
|
||||
ok(restoreOneButton.classList.contains("primary"), "Restore Tab button should have the primary class");
|
||||
|
||||
let newTab2 = gBrowser.addTab();
|
||||
gBrowser.selectedTab = newTab;
|
||||
|
||||
browser.loadURI(PAGE_2);
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Crash the tab
|
||||
yield crashBrowser(browser);
|
||||
|
||||
doc = browser.contentDocument;
|
||||
restoreAllButton = doc.getElementById("restoreAll");
|
||||
restoreOneButton = doc.getElementById("restoreTab");
|
||||
|
||||
ok(!restoreAllButton.hasAttribute("hidden"), "Restore All button should not be hidden");
|
||||
ok(!(restoreOneButton.classList.contains("primary")), "Restore Tab button should not have the primary class");
|
||||
|
||||
gBrowser.removeTab(newTab);
|
||||
gBrowser.removeTab(newTab2);
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ function* performTest() {
|
|||
let [host, , doc] = yield createHost("bottom", "data:text/html," +
|
||||
"<h1>browser_outputParser.js</h1><div></div>");
|
||||
|
||||
let parser = new OutputParser();
|
||||
let parser = new OutputParser(doc);
|
||||
testParseCssProperty(doc, parser);
|
||||
testParseCssVar(doc, parser);
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@
|
|||
tooltiptext="&visibilityToggle.tooltip;"
|
||||
accesskey="&saveButton.accesskey;"></xul:label>
|
||||
<hgroup class="stylesheet-info">
|
||||
<h1><a class="stylesheet-name" tabindex="0"><xul:label crop="start"/></a></h1>
|
||||
<h1><a class="stylesheet-name" tabindex="0"><xul:label crop="center"/></a></h1>
|
||||
<div class="stylesheet-more">
|
||||
<h3 class="stylesheet-title"></h3>
|
||||
<h3 class="stylesheet-linked-file"></h3>
|
||||
|
|
|
@ -137,7 +137,7 @@ function CssComputedView(inspector, document, pageStyle) {
|
|||
|
||||
this.propertyViews = [];
|
||||
|
||||
this._outputParser = new OutputParser();
|
||||
this._outputParser = new OutputParser(document);
|
||||
|
||||
let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]
|
||||
.getService(Ci.nsIXULChromeRegistry);
|
||||
|
|
|
@ -334,7 +334,11 @@ ElementStyle.prototype = {
|
|||
let textProps = [];
|
||||
for (let rule of this.rules) {
|
||||
if (rule.pseudoElement == pseudo && !rule.keyframes) {
|
||||
textProps = textProps.concat(rule.textProps.slice(0).reverse());
|
||||
for (let textProp of rule.textProps.slice(0).reverse()) {
|
||||
if (textProp.enabled) {
|
||||
textProps.push(textProp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1161,7 +1165,7 @@ function CssRuleView(inspector, document, aStore, aPageStyle) {
|
|||
this.store = aStore || {};
|
||||
this.pageStyle = aPageStyle;
|
||||
|
||||
this._outputParser = new OutputParser();
|
||||
this._outputParser = new OutputParser(document);
|
||||
|
||||
this._onKeypress = this._onKeypress.bind(this);
|
||||
this._onAddRule = this._onAddRule.bind(this);
|
||||
|
@ -3163,7 +3167,8 @@ TextPropertyEditor.prototype = {
|
|||
},
|
||||
|
||||
_onStartEditing: function() {
|
||||
this._previewValue(this.prop.value);
|
||||
this.element.classList.remove("ruleview-overridden");
|
||||
this.enable.style.visibility = "hidden";
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -3468,9 +3473,6 @@ TextPropertyEditor.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
this.element.classList.remove("ruleview-overridden");
|
||||
this.enable.style.visibility = "hidden";
|
||||
|
||||
let val = parseSingleValue(aValue);
|
||||
this.ruleEditor.rule.previewPropertyValue(this.prop, val.value,
|
||||
val.priority);
|
||||
|
|
|
@ -112,6 +112,11 @@ skip-if = (os == "win" && debug) || e10s # bug 963492: win. bug 1040653: e10s.
|
|||
[browser_ruleview_keyframes-rule_01.js]
|
||||
[browser_ruleview_keyframes-rule_02.js]
|
||||
[browser_ruleview_livepreview.js]
|
||||
[browser_ruleview_mark_overridden_01.js]
|
||||
[browser_ruleview_mark_overridden_02.js]
|
||||
[browser_ruleview_mark_overridden_03.js]
|
||||
[browser_ruleview_mark_overridden_04.js]
|
||||
[browser_ruleview_mark_overridden_05.js]
|
||||
[browser_ruleview_mathml-element.js]
|
||||
[browser_ruleview_media-queries.js]
|
||||
[browser_ruleview_multiple-properties-duplicates.js]
|
||||
|
@ -122,7 +127,6 @@ skip-if = (os == "win" && debug) || e10s # bug 963492: win. bug 1040653: e10s.
|
|||
[browser_ruleview_multiple_properties_02.js]
|
||||
[browser_ruleview_original-source-link.js]
|
||||
[browser_ruleview_cycle-color.js]
|
||||
[browser_ruleview_override.js]
|
||||
[browser_ruleview_pseudo-element_01.js]
|
||||
[browser_ruleview_pseudo-element_02.js]
|
||||
skip-if = e10s # Bug 1090340
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// Tests that a disabled property is previewed when the property name or value
|
||||
// editor is focused and the property remains disabled when the escaping out of
|
||||
// Tests that a disabled property remains disabled when the escaping out of
|
||||
// the property editor.
|
||||
|
||||
let TEST_URI = [
|
||||
|
@ -39,20 +38,19 @@ function* testDisableProperty(inspector, view) {
|
|||
});
|
||||
is(newValue, "", "background-color should have been unset.");
|
||||
|
||||
yield testPreviewDisableProperty(view, ruleEditor, propEditor,
|
||||
yield testEditDisableProperty(view, ruleEditor, propEditor,
|
||||
propEditor.nameSpan, "VK_ESCAPE");
|
||||
yield testPreviewDisableProperty(view, ruleEditor, propEditor,
|
||||
yield testEditDisableProperty(view, ruleEditor, propEditor,
|
||||
propEditor.valueSpan, "VK_ESCAPE");
|
||||
yield testPreviewDisableProperty(view, ruleEditor, propEditor,
|
||||
yield testEditDisableProperty(view, ruleEditor, propEditor,
|
||||
propEditor.valueSpan, "VK_TAB");
|
||||
yield testPreviewDisableProperty(view, ruleEditor, propEditor,
|
||||
yield testEditDisableProperty(view, ruleEditor, propEditor,
|
||||
propEditor.valueSpan, "VK_RETURN");
|
||||
}
|
||||
|
||||
function* testPreviewDisableProperty(view, ruleEditor, propEditor,
|
||||
function* testEditDisableProperty(view, ruleEditor, propEditor,
|
||||
editableField, commitKey) {
|
||||
let editor = yield focusEditableField(view, editableField);
|
||||
yield ruleEditor.rule._applyingModifications;
|
||||
|
||||
ok(!propEditor.element.classList.contains("ruleview-overridden"),
|
||||
"property is not overridden.");
|
||||
|
@ -64,7 +62,7 @@ function* testPreviewDisableProperty(view, ruleEditor, propEditor,
|
|||
ruleIndex: 0,
|
||||
name: "background-color"
|
||||
});
|
||||
is(newValue, "blue", "background-color should have been previewed.");
|
||||
is(newValue, "", "background-color should remain unset.");
|
||||
|
||||
let onBlur = once(editor.input, "blur");
|
||||
EventUtils.synthesizeKey(commitKey, {}, view.styleWindow);
|
||||
|
@ -84,5 +82,5 @@ function* testPreviewDisableProperty(view, ruleEditor, propEditor,
|
|||
ruleIndex: 0,
|
||||
name: "background-color"
|
||||
});
|
||||
is(newValue, "", "background-color should have been unset.");
|
||||
is(newValue, "", "background-color should remain unset.");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that the rule view marks overridden rules correctly based on the
|
||||
// specificity of the rule
|
||||
|
||||
let TEST_URI = [
|
||||
"<style type='text/css'>",
|
||||
"#testid {",
|
||||
" background-color: blue;",
|
||||
"}",
|
||||
".testclass {",
|
||||
" background-color: green;",
|
||||
"}",
|
||||
"</style>",
|
||||
"<div id='testid' class='testclass'>Styled Node</div>"
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testMarkOverridden(inspector, view);
|
||||
});
|
||||
|
||||
function* testMarkOverridden(inspector, view) {
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let idRule = elementStyle.rules[1];
|
||||
let idProp = idRule.textProps[0];
|
||||
is(idProp.name, "background-color",
|
||||
"First ID property should be background-color");
|
||||
is(idProp.value, "blue", "First ID property value should be blue");
|
||||
ok(!idProp.overridden, "ID prop should not be overridden.");
|
||||
ok(!idProp.editor.element.classList.contains("ruleview-overridden"),
|
||||
"ID property editor should not have ruleview-overridden class");
|
||||
|
||||
let classRule = elementStyle.rules[2];
|
||||
let classProp = classRule.textProps[0];
|
||||
is(classProp.name, "background-color",
|
||||
"First class prop should be background-color");
|
||||
is(classProp.value, "green", "First class property value should be green");
|
||||
ok(classProp.overridden, "Class property should be overridden.");
|
||||
ok(classProp.editor.element.classList.contains("ruleview-overridden"),
|
||||
"Class property editor should have ruleview-overridden class");
|
||||
|
||||
// Override background-color by changing the element style.
|
||||
let elementRule = elementStyle.rules[0];
|
||||
elementRule.createProperty("background-color", "purple", "");
|
||||
yield elementRule._applyingModifications;
|
||||
|
||||
let elementProp = elementRule.textProps[0];
|
||||
ok(!elementProp.overridden,
|
||||
"Element style property should not be overridden");
|
||||
ok(idProp.overridden, "ID property should be overridden");
|
||||
ok(idProp.editor.element.classList.contains("ruleview-overridden"),
|
||||
"ID property editor should have ruleview-overridden class");
|
||||
ok(classProp.overridden, "Class property should be overridden");
|
||||
ok(classProp.editor.element.classList.contains("ruleview-overridden"),
|
||||
"Class property editor should have ruleview-overridden class");
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that the rule view marks overridden rules correctly for short hand
|
||||
// properties and the computed list properties
|
||||
|
||||
let TEST_URI = [
|
||||
"<style type='text/css'>",
|
||||
"#testid {",
|
||||
" margin-left: 1px;",
|
||||
"}",
|
||||
".testclass {",
|
||||
" margin: 2px;",
|
||||
"}",
|
||||
"</style>",
|
||||
"<div id='testid' class='testclass'>Styled Node</div>"
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testMarkOverridden(inspector, view);
|
||||
});
|
||||
|
||||
function* testMarkOverridden(inspector, view) {
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let classRule = elementStyle.rules[2];
|
||||
let classProp = classRule.textProps[0];
|
||||
ok(!classProp.overridden,
|
||||
"Class prop shouldn't be overridden, some props are still being used.");
|
||||
|
||||
for (let computed of classProp.computed) {
|
||||
if (computed.name.indexOf("margin-left") == 0) {
|
||||
ok(computed.overridden, "margin-left props should be overridden.");
|
||||
} else {
|
||||
ok(!computed.overridden,
|
||||
"Non-margin-left props should not be overridden.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that the rule view marks overridden rules correctly based on the
|
||||
// priority for the rule
|
||||
|
||||
let TEST_URI = [
|
||||
"<style type='text/css'>",
|
||||
"#testid {",
|
||||
" background-color: blue;",
|
||||
"}",
|
||||
".testclass {",
|
||||
" background-color: green !important;",
|
||||
"}",
|
||||
"</style>",
|
||||
"<div id='testid' class='testclass'>Styled Node</div>"
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testMarkOverridden(inspector, view);
|
||||
});
|
||||
|
||||
function* testMarkOverridden(inspector, view) {
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let idRule = elementStyle.rules[1];
|
||||
let idProp = idRule.textProps[0];
|
||||
ok(idProp.overridden, "Not-important rule should be overridden.");
|
||||
|
||||
let classRule = elementStyle.rules[2];
|
||||
let classProp = classRule.textProps[0];
|
||||
ok(!classProp.overridden, "Important rule should not be overridden.");
|
||||
|
||||
let elementRule = elementStyle.rules[0];
|
||||
let elementProp = elementRule.createProperty("background-color", "purple",
|
||||
"important");
|
||||
yield elementRule._applyingModifications;
|
||||
|
||||
ok(!elementProp.overridden, "New important prop should not be overriden.");
|
||||
ok(idProp.overridden, "ID property should be overridden.");
|
||||
ok(classProp.overridden, "Class property should be overridden.");
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that the rule view marks overridden rules correctly if a property gets
|
||||
// disabled
|
||||
|
||||
let TEST_URI = [
|
||||
"<style type='text/css'>",
|
||||
"#testid {",
|
||||
" background-color: blue;",
|
||||
"}",
|
||||
".testclass {",
|
||||
" background-color: green;",
|
||||
"}",
|
||||
"</style>",
|
||||
"<div id='testid' class='testclass'>Styled Node</div>"
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testMarkOverridden(inspector, view);
|
||||
});
|
||||
|
||||
function* testMarkOverridden(inspector, view) {
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let idRule = elementStyle.rules[1];
|
||||
let idProp = idRule.textProps[0];
|
||||
|
||||
idProp.setEnabled(false);
|
||||
yield idRule._applyingModifications;
|
||||
|
||||
let classRule = elementStyle.rules[2];
|
||||
let classProp = classRule.textProps[0];
|
||||
ok(!classProp.overridden,
|
||||
"Class prop should not be overridden after id prop was disabled.");
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that the rule view marks overridden rules correctly based on the
|
||||
// order of the property
|
||||
|
||||
let TEST_URI = [
|
||||
"<style type='text/css'>",
|
||||
"#testid {",
|
||||
" background-color: green;",
|
||||
"}",
|
||||
"</style>",
|
||||
"<div id='testid' class='testclass'>Styled Node</div>"
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testMarkOverridden(inspector, view);
|
||||
});
|
||||
|
||||
function* testMarkOverridden(inspector, view) {
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
yield createNewRuleViewProperty(ruleEditor, "background-color: red;");
|
||||
|
||||
let firstProp = ruleEditor.rule.textProps[0];
|
||||
let secondProp = ruleEditor.rule.textProps[1];
|
||||
|
||||
ok(firstProp.overridden, "First property should be overridden.");
|
||||
ok(!secondProp.overridden, "Second property should not be overridden.");
|
||||
}
|
|
@ -36,9 +36,9 @@ function waitRuleViewChanged(view, n) {
|
|||
}
|
||||
function* testCreateNewMultiUnfinished(inspector, ruleEditor, view) {
|
||||
let onMutation = inspector.once("markupmutation");
|
||||
// There is 6 rule-view updates, one for the rule view creation,
|
||||
// one for each new property and one last for throttle update.
|
||||
let onRuleViewChanged = waitRuleViewChanged(view, 6);
|
||||
// There is 5 rule-view updates, one for the rule view creation,
|
||||
// one for each new property
|
||||
let onRuleViewChanged = waitRuleViewChanged(view, 5);
|
||||
yield createNewRuleViewProperty(ruleEditor,
|
||||
"color:blue;background : orange ; text-align:center; border-color: ");
|
||||
yield onMutation;
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
// Test the display of overridden declarations in the rule-view
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8,browser_ruleview_override.js");
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
|
||||
yield simpleOverride(inspector, view);
|
||||
yield partialOverride(inspector, view);
|
||||
yield importantOverride(inspector, view);
|
||||
yield disableOverride(inspector, view);
|
||||
});
|
||||
|
||||
function* createTestContent(inspector, style) {
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
let styleNode = addStyle(content.document, style);
|
||||
content.document.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
|
||||
yield onMutated;
|
||||
yield selectNode("#testid", inspector);
|
||||
return styleNode;
|
||||
}
|
||||
|
||||
function* removeTestContent(inspector, node) {
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
node.remove();
|
||||
yield onMutated;
|
||||
}
|
||||
|
||||
function* simpleOverride(inspector, view) {
|
||||
let styleNode = yield createTestContent(inspector, '' +
|
||||
'#testid {' +
|
||||
' background-color: blue;' +
|
||||
'} ' +
|
||||
'.testclass {' +
|
||||
' background-color: green;' +
|
||||
'}');
|
||||
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let idRule = elementStyle.rules[1];
|
||||
let idProp = idRule.textProps[0];
|
||||
is(idProp.name, "background-color", "First ID prop should be background-color");
|
||||
ok(!idProp.overridden, "ID prop should not be overridden.");
|
||||
|
||||
let classRule = elementStyle.rules[2];
|
||||
let classProp = classRule.textProps[0];
|
||||
is(classProp.name, "background-color", "First class prop should be background-color");
|
||||
ok(classProp.overridden, "Class property should be overridden.");
|
||||
|
||||
// Override background-color by changing the element style.
|
||||
let elementRule = elementStyle.rules[0];
|
||||
elementRule.createProperty("background-color", "purple", "");
|
||||
yield elementRule._applyingModifications;
|
||||
|
||||
let elementProp = elementRule.textProps[0];
|
||||
is(classProp.name, "background-color", "First element prop should now be background-color");
|
||||
ok(!elementProp.overridden, "Element style property should not be overridden");
|
||||
ok(idProp.overridden, "ID property should be overridden");
|
||||
ok(classProp.overridden, "Class property should be overridden");
|
||||
|
||||
yield removeTestContent(inspector, styleNode);
|
||||
}
|
||||
|
||||
function* partialOverride(inspector, view) {
|
||||
let styleNode = yield createTestContent(inspector, '' +
|
||||
// Margin shorthand property...
|
||||
'.testclass {' +
|
||||
' margin: 2px;' +
|
||||
'}' +
|
||||
// ... will be partially overridden.
|
||||
'#testid {' +
|
||||
' margin-left: 1px;' +
|
||||
'}');
|
||||
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let classRule = elementStyle.rules[2];
|
||||
let classProp = classRule.textProps[0];
|
||||
ok(!classProp.overridden,
|
||||
"Class prop shouldn't be overridden, some props are still being used.");
|
||||
|
||||
for (let computed of classProp.computed) {
|
||||
if (computed.name.indexOf("margin-left") == 0) {
|
||||
ok(computed.overridden, "margin-left props should be overridden.");
|
||||
} else {
|
||||
ok(!computed.overridden, "Non-margin-left props should not be overridden.");
|
||||
}
|
||||
}
|
||||
|
||||
yield removeTestContent(inspector, styleNode);
|
||||
}
|
||||
|
||||
function* importantOverride(inspector, view) {
|
||||
let styleNode = yield createTestContent(inspector, '' +
|
||||
// Margin shorthand property...
|
||||
'.testclass {' +
|
||||
' background-color: green !important;' +
|
||||
'}' +
|
||||
// ... will be partially overridden.
|
||||
'#testid {' +
|
||||
' background-color: blue;' +
|
||||
'}');
|
||||
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let idRule = elementStyle.rules[1];
|
||||
let idProp = idRule.textProps[0];
|
||||
ok(idProp.overridden, "Not-important rule should be overridden.");
|
||||
|
||||
let classRule = elementStyle.rules[2];
|
||||
let classProp = classRule.textProps[0];
|
||||
ok(!classProp.overridden, "Important rule should not be overridden.");
|
||||
|
||||
yield removeTestContent(inspector, styleNode);
|
||||
|
||||
let elementRule = elementStyle.rules[0];
|
||||
let elementProp = elementRule.createProperty("background-color", "purple", "important");
|
||||
yield elementRule._applyingModifications;
|
||||
|
||||
ok(classProp.overridden, "New important prop should override class property.");
|
||||
ok(!elementProp.overridden, "New important prop should not be overriden.");
|
||||
}
|
||||
|
||||
function* disableOverride(inspector, view) {
|
||||
let styleNode = yield createTestContent(inspector, '' +
|
||||
'#testid {' +
|
||||
' background-color: blue;' +
|
||||
'}' +
|
||||
'.testclass {' +
|
||||
' background-color: green;' +
|
||||
'}');
|
||||
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let idRule = elementStyle.rules[1];
|
||||
let idProp = idRule.textProps[0];
|
||||
|
||||
idProp.setEnabled(false);
|
||||
yield idRule._applyingModifications;
|
||||
|
||||
let classRule = elementStyle.rules[2];
|
||||
let classProp = classRule.textProps[0];
|
||||
ok(!classProp.overridden, "Class prop should not be overridden after id prop was disabled.");
|
||||
|
||||
yield removeTestContent(inspector, styleNode);
|
||||
}
|
|
@ -310,7 +310,7 @@ function test() {
|
|||
}
|
||||
];
|
||||
|
||||
let parser = new OutputParser();
|
||||
let parser = new OutputParser(document);
|
||||
for (let i = 0; i < testData.length; i ++) {
|
||||
let data = testData[i];
|
||||
info("Output-parser test data " + i + ". {" + data.name + " : " + data.value + ";}");
|
||||
|
|
|
@ -414,29 +414,15 @@ function* waitForComputedStyleProperty(selector, pseudo, name, expected) {
|
|||
* @return a promise that resolves to the inplace-editor element when ready
|
||||
*/
|
||||
let focusEditableField = Task.async(function*(ruleView, editable, xOffset=1, yOffset=1, options={}) {
|
||||
// Focusing the name or value input is going to fire a preview and update the rule view
|
||||
let expectRuleViewUpdate =
|
||||
editable.classList.contains("ruleview-propertyname") ||
|
||||
editable.classList.contains("ruleview-propertyvalue");
|
||||
let onRuleViewChanged;
|
||||
if (expectRuleViewUpdate) {
|
||||
onRuleViewChanged = ruleView.once("ruleview-changed");
|
||||
}
|
||||
|
||||
let onFocus = once(editable.parentNode, "focus", true);
|
||||
info("Clicking on editable field to turn to edit mode");
|
||||
EventUtils.synthesizeMouse(editable, xOffset, yOffset, options,
|
||||
editable.ownerDocument.defaultView);
|
||||
let event = yield onFocus;
|
||||
yield onFocus;
|
||||
|
||||
info("Editable field gained focus, returning the input field now");
|
||||
let onEdit = inplaceEditor(editable.ownerDocument.activeElement);
|
||||
|
||||
if (expectRuleViewUpdate) {
|
||||
info("Waiting for rule view update");
|
||||
yield onRuleViewChanged;
|
||||
}
|
||||
|
||||
return onEdit;
|
||||
});
|
||||
|
||||
|
@ -801,11 +787,11 @@ function getRuleViewRuleEditor(view, childrenIndex, nodeIndex) {
|
|||
let focusNewRuleViewProperty = Task.async(function*(ruleEditor) {
|
||||
info("Clicking on a close ruleEditor brace to start editing a new property");
|
||||
ruleEditor.closeBrace.scrollIntoView();
|
||||
let editor = yield focusEditableField(ruleEditor.ruleView, ruleEditor.closeBrace);
|
||||
let editor = yield focusEditableField(ruleEditor.ruleView,
|
||||
ruleEditor.closeBrace);
|
||||
|
||||
is(inplaceEditor(ruleEditor.newPropSpan), editor, "Focused editor is the new property editor.");
|
||||
is(ruleEditor.rule.textProps.length, 0, "Starting with one new text property.");
|
||||
is(ruleEditor.propertyList.children.length, 1, "Starting with two property editors.");
|
||||
is(inplaceEditor(ruleEditor.newPropSpan), editor,
|
||||
"Focused editor is the new property editor.");
|
||||
|
||||
return editor;
|
||||
});
|
||||
|
|
|
@ -680,6 +680,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
|
|||
|
||||
<!ENTITY identity.connectionSecure "Secure Connection">
|
||||
<!ENTITY identity.connectionNotSecure "Connection is Not Secure">
|
||||
<!ENTITY identity.connectionFile "This page is stored on your computer.">
|
||||
<!ENTITY identity.connectionVerified "&brandShortName; verified that you are securely connected to this site, run by:">
|
||||
<!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
|
||||
|
||||
|
|
|
@ -88,7 +88,12 @@ this.TabCrashReporter = {
|
|||
}
|
||||
},
|
||||
|
||||
onAboutTabCrashedLoad: function (aBrowser) {
|
||||
onAboutTabCrashedLoad: function (aBrowser, aParams) {
|
||||
// If there was only one tab open that crashed, do not show the "restore all tabs" button
|
||||
if (aParams.crashedTabCount == 1) {
|
||||
this.hideRestoreAllButton(aBrowser);
|
||||
}
|
||||
|
||||
if (!this.childMap)
|
||||
return;
|
||||
|
||||
|
@ -97,6 +102,11 @@ this.TabCrashReporter = {
|
|||
return;
|
||||
|
||||
aBrowser.contentDocument.documentElement.classList.add("crashDumpAvailable");
|
||||
},
|
||||
|
||||
hideRestoreAllButton: function (aBrowser) {
|
||||
aBrowser.contentDocument.getElementById("restoreAll").setAttribute("hidden", true);
|
||||
aBrowser.contentDocument.getElementById("restoreTab").setAttribute("class", "primary");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -146,6 +146,7 @@ browser.jar:
|
|||
skin/classic/browser/loop/toolbar-inverted@2x.png (loop/toolbar-inverted@2x.png)
|
||||
* skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css)
|
||||
skin/classic/browser/controlcenter/arrow-subview.svg (../shared/controlcenter/arrow-subview.svg)
|
||||
skin/classic/browser/controlcenter/arrow-subview-back.svg (../shared/controlcenter/arrow-subview-back.svg)
|
||||
skin/classic/browser/controlcenter/conn-not-secure.svg (../shared/controlcenter/conn-not-secure.svg)
|
||||
skin/classic/browser/controlcenter/conn-secure.svg (../shared/controlcenter/conn-secure.svg)
|
||||
skin/classic/browser/controlcenter/conn-degraded.svg (../shared/controlcenter/conn-degraded.svg)
|
||||
|
|
|
@ -190,6 +190,7 @@ browser.jar:
|
|||
skin/classic/browser/yosemite/loop/toolbar@2x.png (loop/toolbar-yosemite@2x.png)
|
||||
* skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css)
|
||||
skin/classic/browser/controlcenter/arrow-subview.svg (../shared/controlcenter/arrow-subview.svg)
|
||||
skin/classic/browser/controlcenter/arrow-subview-back.svg (../shared/controlcenter/arrow-subview-back.svg)
|
||||
skin/classic/browser/controlcenter/conn-not-secure.svg (../shared/controlcenter/conn-not-secure.svg)
|
||||
skin/classic/browser/controlcenter/conn-secure.svg (../shared/controlcenter/conn-secure.svg)
|
||||
skin/classic/browser/controlcenter/conn-degraded.svg (../shared/controlcenter/conn-degraded.svg)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<polygon fill="#fff" points="12,3.5 10.5,2 4.625,8 10.5,14 12,12.5 7.625,8" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 423 B |
|
@ -12,11 +12,16 @@
|
|||
/* Show the "Connection is not secure" labels only for non-secure sites. */
|
||||
#identity-popup-security-content:not(.unknownIdentity) > .identity-popup-connection-not-secure,
|
||||
#identity-popup-securityView:not(.unknownIdentity) > #identity-popup-securityView-header > .identity-popup-connection-not-secure,
|
||||
/* Show "This page is stored on your computer" only for file URLs. */
|
||||
#identity-popup-security-content:not(.fileURI) > .identity-popup-connection-file-uri,
|
||||
#identity-popup-securityView:not(.fileURI) > #identity-popup-securityView-header > .identity-popup-connection-file-uri,
|
||||
/* Show "This is a secure internal page" only for whitelisted pages. */
|
||||
#identity-popup-securityView:not(.chromeUI) > #identity-popup-securityView-header > .identity-popup-connection-internal,
|
||||
#identity-popup-security-content:not(.chromeUI) > .identity-popup-connection-internal,
|
||||
/* Hide the subsection arrow for whitelisted chromeUI pages. */
|
||||
#identity-popup-security-content.chromeUI + .identity-popup-expander,
|
||||
/* Hide the subsection arrow for whitelisted file URI pages. */
|
||||
#identity-popup-security-content.fileURI + .identity-popup-expander,
|
||||
/* Hide the tracking protection section for whitelisted chromeUI pages. */
|
||||
#identity-popup-mainView.chromeUI > #tracking-protection-container {
|
||||
display: none;
|
||||
|
@ -111,16 +116,8 @@
|
|||
.identity-popup-expander[panel-multiview-anchor] {
|
||||
transition: background-color 250ms ease-in;
|
||||
background-color: Highlight;
|
||||
background-image: url("chrome://browser/skin/customizableui/subView-arrow-back-inverted.png"),
|
||||
background-image: url("chrome://browser/skin/controlcenter/arrow-subview-back.svg"),
|
||||
linear-gradient(rgba(255,255,255,0.3), transparent);
|
||||
color: HighlightText;
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
.identity-popup-expander[panel-multiview-anchor] {
|
||||
background-image: url("chrome://browser/skin/customizableui/subView-arrow-back-inverted@2x.png"),
|
||||
linear-gradient(rgba(255,255,255,0.3), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.identity-popup-expander > .button-box {
|
||||
|
|
|
@ -202,6 +202,7 @@ browser.jar:
|
|||
skin/classic/browser/loop/toolbar-XP@2x.png (loop/toolbar-XP@2x.png)
|
||||
* skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css)
|
||||
skin/classic/browser/controlcenter/arrow-subview.svg (../shared/controlcenter/arrow-subview.svg)
|
||||
skin/classic/browser/controlcenter/arrow-subview-back.svg (../shared/controlcenter/arrow-subview-back.svg)
|
||||
skin/classic/browser/controlcenter/conn-not-secure.svg (../shared/controlcenter/conn-not-secure.svg)
|
||||
skin/classic/browser/controlcenter/conn-degraded.svg (../shared/controlcenter/conn-degraded.svg)
|
||||
skin/classic/browser/controlcenter/conn-secure.svg (../shared/controlcenter/conn-secure.svg)
|
||||
|
|
|
@ -700,15 +700,19 @@ BluetoothHfpManager::HandleVoiceConnectionChanged(uint32_t aClientId)
|
|||
// Signal
|
||||
JS::Rooted<JS::Value> value(nsContentUtils::RootingCxForThread());
|
||||
voiceInfo->GetRelSignalStrength(&value);
|
||||
NS_ENSURE_TRUE_VOID(value.isNumber());
|
||||
mSignal = (int)ceil(value.toNumber() / 20.0);
|
||||
if (value.isNumber()) {
|
||||
mSignal = (int)ceil(value.toNumber() / 20.0);
|
||||
}
|
||||
|
||||
UpdateDeviceCIND();
|
||||
|
||||
// Operator name
|
||||
nsCOMPtr<nsIMobileNetworkInfo> network;
|
||||
voiceInfo->GetNetwork(getter_AddRefs(network));
|
||||
NS_ENSURE_TRUE_VOID(network);
|
||||
if (!network) {
|
||||
BT_LOGD("Unable to get network information");
|
||||
return;
|
||||
}
|
||||
network->GetLongName(mOperatorName);
|
||||
|
||||
// According to GSM 07.07, "<format> indicates if the format is alphanumeric
|
||||
|
|
|
@ -1008,6 +1008,11 @@ BluetoothAdapter::HandlePropertyChanged(const BluetoothValue& aValue)
|
|||
}
|
||||
}
|
||||
|
||||
if (types.IsEmpty()) {
|
||||
// No adapter attribute changed
|
||||
return;
|
||||
}
|
||||
|
||||
DispatchAttributeEvent(types);
|
||||
}
|
||||
|
||||
|
@ -1134,7 +1139,7 @@ BluetoothAdapter::HandleDeviceUnpaired(const BluetoothValue& aValue)
|
|||
void
|
||||
BluetoothAdapter::DispatchAttributeEvent(const Sequence<nsString>& aTypes)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(aTypes.Length());
|
||||
MOZ_ASSERT(!aTypes.IsEmpty());
|
||||
|
||||
BluetoothAttributeEventInit init;
|
||||
init.mAttrs = aTypes;
|
||||
|
|
|
@ -285,6 +285,8 @@ private:
|
|||
|
||||
/**
|
||||
* Fire BluetoothAttributeEvent to trigger onattributechanged event handler.
|
||||
*
|
||||
* @param aTypes [in] Array of changed attributes. Must be non-empty.
|
||||
*/
|
||||
void DispatchAttributeEvent(const Sequence<nsString>& aTypes);
|
||||
|
||||
|
|
|
@ -293,13 +293,18 @@ BluetoothDevice::HandlePropertyChanged(const BluetoothValue& aValue)
|
|||
}
|
||||
}
|
||||
|
||||
if (types.IsEmpty()) {
|
||||
// No device attribute changed
|
||||
return;
|
||||
}
|
||||
|
||||
DispatchAttributeEvent(types);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothDevice::DispatchAttributeEvent(const Sequence<nsString>& aTypes)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(aTypes.Length());
|
||||
MOZ_ASSERT(!aTypes.IsEmpty());
|
||||
|
||||
BluetoothAttributeEventInit init;
|
||||
init.mAttrs = aTypes;
|
||||
|
|
|
@ -117,6 +117,8 @@ private:
|
|||
|
||||
/**
|
||||
* Fire BluetoothAttributeEvent to trigger onattributechanged event handler.
|
||||
*
|
||||
* @param aTypes [in] Array of changed attributes. Must be non-empty.
|
||||
*/
|
||||
void DispatchAttributeEvent(const Sequence<nsString>& aTypes);
|
||||
|
||||
|
|
|
@ -651,9 +651,10 @@ BluetoothHfpManager::HandleVoiceConnectionChanged(uint32_t aClientId)
|
|||
|
||||
JS::Rooted<JS::Value> value(nsContentUtils::RootingCxForThread());
|
||||
voiceInfo->GetRelSignalStrength(&value);
|
||||
NS_ENSURE_TRUE_VOID(value.isNumber());
|
||||
uint8_t signal = ceil(value.toNumber() / 20.0);
|
||||
UpdateCIND(CINDType::SIGNAL, signal);
|
||||
if (value.isNumber()) {
|
||||
uint8_t signal = ceil(value.toNumber() / 20.0);
|
||||
UpdateCIND(CINDType::SIGNAL, signal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible return values for mode are:
|
||||
|
@ -667,7 +668,10 @@ BluetoothHfpManager::HandleVoiceConnectionChanged(uint32_t aClientId)
|
|||
|
||||
nsCOMPtr<nsIMobileNetworkInfo> network;
|
||||
voiceInfo->GetNetwork(getter_AddRefs(network));
|
||||
NS_ENSURE_TRUE_VOID(network);
|
||||
if (!network) {
|
||||
BT_LOGD("Unable to get network information");
|
||||
return;
|
||||
}
|
||||
network->GetLongName(mOperatorName);
|
||||
|
||||
// According to GSM 07.07, "<format> indicates if the format is alphanumeric
|
||||
|
|
|
@ -37,6 +37,8 @@ public class RestrictedProfiles {
|
|||
add("wyciwyg");
|
||||
}};
|
||||
|
||||
private static final String ABOUT_ADDONS = "about:addons";
|
||||
|
||||
/**
|
||||
* This is a hack to allow non-GeckoApp activities to safely call into
|
||||
* RestrictedProfiles without reworking this class or GeckoProfile.
|
||||
|
@ -271,6 +273,13 @@ public class RestrictedProfiles {
|
|||
return !GUEST_RESTRICTIONS.contains(restriction);
|
||||
}
|
||||
|
||||
// Disallow browsing about:addons if 'disallow install extension' restriction is enforced
|
||||
if (restriction == Restriction.DISALLOW_BROWSE_FILES
|
||||
&& url.toLowerCase().startsWith(ABOUT_ADDONS)
|
||||
&& !isAllowed(context, Restriction.DISALLOW_INSTALL_EXTENSION)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: Restrictions hold the opposite intention, so we need to flip it.
|
||||
return !getRestriction(context, restriction);
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ public class TabMenuStrip extends HorizontalScrollView
|
|||
super.draw(canvas);
|
||||
|
||||
final int height = getHeight();
|
||||
canvas.drawRect(0, height - shadowSize, getWidth(), height, shadowPaint);
|
||||
canvas.drawRect(0, height - shadowSize, layout.getWidth(), height, shadowPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -566,8 +566,8 @@ just addresses the organization to follow, e.g. "This site is run by " -->
|
|||
<!ENTITY doorhanger_tracking_title "Tracking protection">
|
||||
<!ENTITY doorhanger_tracking_state_enabled "Enabled">
|
||||
<!ENTITY doorhanger_tracking_state_disabled "Disabled">
|
||||
<!ENTITY doorhanger_tracking_message_enabled "Blocking tracking elements that may affect your browsing experience.">
|
||||
<!ENTITY doorhanger_tracking_message_disabled "Attempts to track your online behavior are not being blocked.">
|
||||
<!ENTITY doorhanger_tracking_message_enabled1 "Attempts to track your online behavior have been blocked.">
|
||||
<!ENTITY doorhanger_tracking_message_disabled1 "This site includes content that tracks your browsing.">
|
||||
|
||||
<!-- Common mixed and tracking content strings in site identity popup -->
|
||||
<!ENTITY learn_more "Learn More">
|
||||
|
|
|
@ -472,8 +472,8 @@
|
|||
<string name="doorhanger_tracking_title">&doorhanger_tracking_title;</string>
|
||||
<string name="doorhanger_tracking_state_enabled">&doorhanger_tracking_state_enabled;</string>
|
||||
<string name="doorhanger_tracking_state_disabled">&doorhanger_tracking_state_disabled;</string>
|
||||
<string name="doorhanger_tracking_message_enabled">&doorhanger_tracking_message_enabled;</string>
|
||||
<string name="doorhanger_tracking_message_disabled">&doorhanger_tracking_message_disabled;</string>
|
||||
<string name="doorhanger_tracking_message_enabled">&doorhanger_tracking_message_enabled1;</string>
|
||||
<string name="doorhanger_tracking_message_disabled">&doorhanger_tracking_message_disabled1;</string>
|
||||
|
||||
<string name="learn_more">&learn_more;</string>
|
||||
<string name="enable_protection">&enable_protection;</string>
|
||||
|
|
|
@ -3050,11 +3050,6 @@ var NativeWindow = {
|
|||
clipboard.copyString(aString);
|
||||
},
|
||||
|
||||
_shareStringWithDefault: function(aSharedString, aTitle) {
|
||||
let sharing = Cc["@mozilla.org/uriloader/external-sharing-app-service;1"].getService(Ci.nsIExternalSharingAppService);
|
||||
sharing.shareWithDefault(aSharedString, "text/plain", aTitle);
|
||||
},
|
||||
|
||||
_stripScheme: function(aString) {
|
||||
let index = aString.indexOf(":");
|
||||
return aString.slice(index + 1);
|
||||
|
@ -6017,6 +6012,10 @@ var FormAssistant = {
|
|||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
if (this._isDisabledElement(aElement)) {
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't display the form auto-complete popup after the user starts typing
|
||||
// to avoid confusing somes IME. See bug 758820 and bug 632744.
|
||||
|
@ -6083,6 +6082,17 @@ var FormAssistant = {
|
|||
|
||||
_hideFormAssistPopup: function _hideFormAssistPopup() {
|
||||
Messaging.sendRequest({ type: "FormAssist:Hide" });
|
||||
},
|
||||
|
||||
_isDisabledElement : function(aElement) {
|
||||
let currentElement = aElement;
|
||||
while (currentElement) {
|
||||
if(currentElement.disabled)
|
||||
return true;
|
||||
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -40,6 +40,12 @@ public class TestJarReader extends InstrumentationTestCase {
|
|||
stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
|
||||
assertNull(stream);
|
||||
|
||||
// Test looking for a file that doesn't exist in the APK.
|
||||
// Bug 1174922, prefixed string / length error.
|
||||
url = "jar:file://" + appPath + "!/" + AppConstants.OMNIJAR_NAME + "BAD";
|
||||
stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
|
||||
assertNull(stream);
|
||||
|
||||
// Test looking for an jar with an invalid url.
|
||||
url = "jar:file://" + appPath + "!" + "!/" + AppConstants.OMNIJAR_NAME;
|
||||
stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/nonexistent_file.png");
|
||||
|
|
|
@ -16,14 +16,12 @@ import android.content.Context;
|
|||
* as loading some invalid jar urls.
|
||||
*/
|
||||
public class testJarReader extends BaseTest {
|
||||
public void testGetJarURL() {
|
||||
public void testJarReader() {
|
||||
// Invalid characters are escaped.
|
||||
final String s = GeckoJarReader.computeJarURI("some[1].apk", "something/else");
|
||||
mAsserter.ok(!s.contains("["), "Illegal characters are escaped away.", null);
|
||||
mAsserter.ok(!s.toLowerCase().contains("%2f"), "Path characters aren't escaped.", null);
|
||||
}
|
||||
|
||||
public void testJarReader() {
|
||||
final Context context = getInstrumentation().getTargetContext().getApplicationContext();
|
||||
String appPath = getActivity().getApplication().getPackageResourcePath();
|
||||
mAsserter.isnot(appPath, null, "getPackageResourcePath is non-null");
|
||||
|
@ -43,6 +41,12 @@ public class testJarReader extends BaseTest {
|
|||
stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
|
||||
mAsserter.is(stream, null, "JarReader returned null for valid file in invalid jar file");
|
||||
|
||||
// Test looking for a file that doesn't exist in the APK.
|
||||
// Bug 1174922, prefixed string / length error.
|
||||
url = "jar:file://" + appPath + "!/" + AppConstants.OMNIJAR_NAME + "BAD";
|
||||
stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
|
||||
mAsserter.is(stream, null, "JarReader returned null for valid file in other invalid jar file");
|
||||
|
||||
// Test looking for an jar with an invalid url.
|
||||
url = "jar:file://" + appPath + "!" + "!/" + AppConstants.OMNIJAR_NAME;
|
||||
stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/nonexistent_file.png");
|
||||
|
|
|
@ -270,7 +270,7 @@ private:
|
|||
*/
|
||||
bool Equals(const char *str) const
|
||||
{
|
||||
return strncmp(str, buf, length) == 0;
|
||||
return (strncmp(str, buf, length) == 0 && str[length] == '\0');
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -499,60 +499,20 @@ FxAccountsInternal.prototype = {
|
|||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* returns a promise that fires with the keypair.
|
||||
*/
|
||||
getKeyPair: Task.async(function* (mustBeValidUntil) {
|
||||
// If the debugging pref to ignore cached authentication credentials is set for Sync,
|
||||
// then don't use any cached key pair, i.e., generate a new one and get it signed.
|
||||
// The purpose of this pref is to expedite any auth errors as the result of a
|
||||
// expired or revoked FxA session token, e.g., from resetting or changing the FxA
|
||||
// password.
|
||||
let ignoreCachedAuthCredentials = false;
|
||||
try {
|
||||
ignoreCachedAuthCredentials = Services.prefs.getBoolPref("services.sync.debug.ignoreCachedAuthCredentials");
|
||||
} catch(e) {
|
||||
// Pref doesn't exist
|
||||
}
|
||||
let currentState = this.currentAccountState;
|
||||
let accountData = yield currentState.getUserAccountData("keyPair");
|
||||
if (!ignoreCachedAuthCredentials && accountData.keyPair && (accountData.keyPair.validUntil > mustBeValidUntil)) {
|
||||
log.debug("getKeyPair: already have a keyPair");
|
||||
return accountData.keyPair.keyPair;
|
||||
}
|
||||
// Otherwse, create a keypair and set validity limit.
|
||||
let willBeValidUntil = this.now() + KEY_LIFETIME;
|
||||
let kp = yield new Promise((resolve, reject) => {
|
||||
jwcrypto.generateKeyPair("DS160", (err, kp) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
log.debug("got keyPair");
|
||||
let toUpdate = {
|
||||
keyPair: {
|
||||
keyPair: kp,
|
||||
validUntil: willBeValidUntil
|
||||
},
|
||||
cert: null
|
||||
};
|
||||
currentState.updateUserAccountData(toUpdate).then(() => {
|
||||
resolve(kp);
|
||||
}).catch(err => {
|
||||
log.error("Failed to update account data with keypair and cert");
|
||||
});
|
||||
});
|
||||
});
|
||||
return kp;
|
||||
}),
|
||||
|
||||
/**
|
||||
* returns a promise that fires with the assertion. If there is no verified
|
||||
* signed-in user, fires with null.
|
||||
*/
|
||||
getAssertion: function getAssertion(audience) {
|
||||
return this._getAssertion(audience);
|
||||
},
|
||||
|
||||
// getAssertion() is "public" so screws with our mock story. This
|
||||
// implementation method *can* be (and is) mocked by tests.
|
||||
_getAssertion: function _getAssertion(audience) {
|
||||
log.debug("enter getAssertion()");
|
||||
let currentState = this.currentAccountState;
|
||||
let mustBeValidUntil = this.now() + ASSERTION_USE_PERIOD;
|
||||
return currentState.getUserAccountData().then(data => {
|
||||
if (!data) {
|
||||
// No signed-in user
|
||||
|
@ -562,12 +522,17 @@ FxAccountsInternal.prototype = {
|
|||
// Signed-in user has not verified email
|
||||
return null;
|
||||
}
|
||||
return this.getKeyPair(mustBeValidUntil).then(keyPair => {
|
||||
return this.getCertificate(data, keyPair, mustBeValidUntil)
|
||||
.then(cert => {
|
||||
return this.getAssertionFromCert(data, keyPair, cert, audience);
|
||||
});
|
||||
});
|
||||
if (!data.sessionToken) {
|
||||
// can't get a signed certificate without a session token, but that
|
||||
// should be impossible - make log noise about it.
|
||||
log.error("getAssertion called without a session token!");
|
||||
return null;
|
||||
}
|
||||
return this.getKeypairAndCertificate(currentState).then(
|
||||
({keyPair, certificate}) => {
|
||||
return this.getAssertionFromCert(data, keyPair, certificate, audience);
|
||||
}
|
||||
);
|
||||
}).then(result => currentState.resolve(result));
|
||||
},
|
||||
|
||||
|
@ -832,34 +797,91 @@ FxAccountsInternal.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* returns a promise that fires with a certificate.
|
||||
* returns a promise that fires with {keyPair, certificate}.
|
||||
*/
|
||||
getCertificate: Task.async(function* (data, keyPair, mustBeValidUntil) {
|
||||
// TODO: get the lifetime from the cert's .exp field
|
||||
let currentState = this.currentAccountState;
|
||||
let accountData = yield currentState.getUserAccountData("cert");
|
||||
if (accountData.cert && accountData.cert.validUntil > mustBeValidUntil) {
|
||||
log.debug(" getCertificate already had one");
|
||||
return accountData.cert.cert;
|
||||
getKeypairAndCertificate: Task.async(function* (currentState) {
|
||||
// If the debugging pref to ignore cached authentication credentials is set for Sync,
|
||||
// then don't use any cached key pair/certificate, i.e., generate a new
|
||||
// one and get it signed.
|
||||
// The purpose of this pref is to expedite any auth errors as the result of a
|
||||
// expired or revoked FxA session token, e.g., from resetting or changing the FxA
|
||||
// password.
|
||||
let ignoreCachedAuthCredentials = false;
|
||||
try {
|
||||
ignoreCachedAuthCredentials = Services.prefs.getBoolPref("services.sync.debug.ignoreCachedAuthCredentials");
|
||||
} catch(e) {
|
||||
// Pref doesn't exist
|
||||
}
|
||||
let mustBeValidUntil = this.now() + ASSERTION_USE_PERIOD;
|
||||
let accountData = yield currentState.getUserAccountData(["cert", "keyPair", "sessionToken"]);
|
||||
|
||||
let keyPairValid = !ignoreCachedAuthCredentials &&
|
||||
accountData.keyPair &&
|
||||
(accountData.keyPair.validUntil > mustBeValidUntil);
|
||||
let certValid = !ignoreCachedAuthCredentials &&
|
||||
accountData.cert &&
|
||||
(accountData.cert.validUntil > mustBeValidUntil);
|
||||
// TODO: get the lifetime from the cert's .exp field
|
||||
if (keyPairValid && certValid) {
|
||||
log.debug("getKeypairAndCertificate: already have keyPair and certificate");
|
||||
return {
|
||||
keyPair: accountData.keyPair.rawKeyPair,
|
||||
certificate: accountData.cert.rawCert
|
||||
}
|
||||
}
|
||||
// We are definately going to generate a new cert, either because it has
|
||||
// already expired, or the keyPair has - and a new keyPair means we must
|
||||
// generate a new cert.
|
||||
|
||||
// A keyPair has a longer lifetime than a cert, so it's possible we will
|
||||
// have a valid keypair but an expired cert, which means we can skip
|
||||
// keypair generation.
|
||||
// Either way, the cert will require hitting the network, so bail now if
|
||||
// we know that's going to fail.
|
||||
if (Services.io.offline) {
|
||||
throw new Error(ERROR_OFFLINE);
|
||||
}
|
||||
let willBeValidUntil = this.now() + CERT_LIFETIME;
|
||||
let cert = yield this.getCertificateSigned(data.sessionToken,
|
||||
keyPair.serializedPublicKey,
|
||||
CERT_LIFETIME);
|
||||
log.debug("getCertificate got a new one: " + !!cert);
|
||||
if (cert) {
|
||||
|
||||
let keyPair;
|
||||
if (keyPairValid) {
|
||||
keyPair = accountData.keyPair;
|
||||
} else {
|
||||
let keyWillBeValidUntil = this.now() + KEY_LIFETIME;
|
||||
keyPair = yield new Promise((resolve, reject) => {
|
||||
jwcrypto.generateKeyPair("DS160", (err, kp) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
log.debug("got keyPair");
|
||||
resolve({
|
||||
rawKeyPair: kp,
|
||||
validUntil: keyWillBeValidUntil,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// and generate the cert.
|
||||
let certWillBeValidUntil = this.now() + CERT_LIFETIME;
|
||||
let certificate = yield this.getCertificateSigned(accountData.sessionToken,
|
||||
keyPair.rawKeyPair.serializedPublicKey,
|
||||
CERT_LIFETIME);
|
||||
log.debug("getCertificate got a new one: " + !!certificate);
|
||||
if (certificate) {
|
||||
// Cache both keypair and cert.
|
||||
let toUpdate = {
|
||||
keyPair,
|
||||
cert: {
|
||||
cert: cert,
|
||||
validUntil: willBeValidUntil
|
||||
}
|
||||
rawCert: certificate,
|
||||
validUntil: certWillBeValidUntil,
|
||||
},
|
||||
};
|
||||
yield currentState.updateUserAccountData(toUpdate);
|
||||
}
|
||||
return cert;
|
||||
return {
|
||||
keyPair: keyPair.rawKeyPair,
|
||||
certificate: certificate,
|
||||
}
|
||||
}),
|
||||
|
||||
getUserAccountData: function() {
|
||||
|
|
|
@ -166,6 +166,22 @@ function MockFxAccounts() {
|
|||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Some tests want a "real" fxa instance - however, we still mock the storage
|
||||
* to keep the tests fast on b2g.
|
||||
*/
|
||||
function MakeFxAccounts(internal = {}) {
|
||||
if (!internal.newAccountState) {
|
||||
// we use a real accountState but mocked storage.
|
||||
internal.newAccountState = function(credentials) {
|
||||
let storage = new MockStorageManager();
|
||||
storage.initialize(credentials);
|
||||
return new AccountState(storage);
|
||||
};
|
||||
}
|
||||
return new FxAccounts(internal);
|
||||
}
|
||||
|
||||
add_test(function test_non_https_remote_server_uri_with_requireHttps_false() {
|
||||
Services.prefs.setBoolPref(
|
||||
"identity.fxaccounts.allowHttp",
|
||||
|
@ -195,16 +211,8 @@ add_test(function test_non_https_remote_server_uri() {
|
|||
});
|
||||
|
||||
add_task(function test_get_signed_in_user_initially_unset() {
|
||||
// This test, unlike many of the the rest, uses a (largely) un-mocked
|
||||
// FxAccounts instance.
|
||||
let account = new FxAccounts({
|
||||
newAccountState(credentials) {
|
||||
// we use a real accountState but mocked storage.
|
||||
let storage = new MockStorageManager();
|
||||
storage.initialize(credentials);
|
||||
return new AccountState(storage);
|
||||
},
|
||||
});
|
||||
_("Check getSignedInUser initially and after signout reports no user");
|
||||
let account = MakeFxAccounts();
|
||||
let credentials = {
|
||||
email: "foo@example.com",
|
||||
uid: "1234@lcip.org",
|
||||
|
@ -241,38 +249,22 @@ add_task(function test_get_signed_in_user_initially_unset() {
|
|||
do_check_eq(result, null);
|
||||
});
|
||||
|
||||
add_task(function* test_getCertificate() {
|
||||
_("getCertificate()");
|
||||
// This test, unlike many of the the rest, uses a (largely) un-mocked
|
||||
// FxAccounts instance.
|
||||
// We do mock the storage to keep the test fast on b2g.
|
||||
let fxa = new FxAccounts({
|
||||
newAccountState(credentials) {
|
||||
// we use a real accountState but mocked storage.
|
||||
let storage = new MockStorageManager();
|
||||
storage.initialize(credentials);
|
||||
return new AccountState(storage);
|
||||
},
|
||||
});
|
||||
add_task(function* test_getCertificateOffline() {
|
||||
_("getCertificateOffline()");
|
||||
let fxa = MakeFxAccounts();
|
||||
let credentials = {
|
||||
email: "foo@example.com",
|
||||
uid: "1234@lcip.org",
|
||||
assertion: "foobar",
|
||||
sessionToken: "dead",
|
||||
kA: "beef",
|
||||
kB: "cafe",
|
||||
verified: true
|
||||
verified: true,
|
||||
};
|
||||
|
||||
yield fxa.setSignedInUser(credentials);
|
||||
|
||||
// Test that an expired cert throws if we're offline.
|
||||
fxa.internal.currentAccountState.cert = {
|
||||
validUntil: Date.parse("Mon, 13 Jan 2000 21:45:06 GMT")
|
||||
};
|
||||
let offline = Services.io.offline;
|
||||
Services.io.offline = true;
|
||||
// This call would break from missing parameters ...
|
||||
yield fxa.internal.getCertificate().then(
|
||||
yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState).then(
|
||||
result => {
|
||||
Services.io.offline = offline;
|
||||
do_throw("Unexpected success");
|
||||
|
@ -283,8 +275,99 @@ add_task(function* test_getCertificate() {
|
|||
do_check_eq(err, "Error: OFFLINE");
|
||||
}
|
||||
);
|
||||
yield fxa.signOut(/*localOnly = */true);
|
||||
});
|
||||
|
||||
add_task(function* test_getCertificateCached() {
|
||||
_("getCertificateCached()");
|
||||
let fxa = MakeFxAccounts();
|
||||
let credentials = {
|
||||
email: "foo@example.com",
|
||||
uid: "1234@lcip.org",
|
||||
sessionToken: "dead",
|
||||
verified: true,
|
||||
// A cached keypair and cert that remain valid.
|
||||
keyPair: {
|
||||
validUntil: Date.now() + KEY_LIFETIME + 10000,
|
||||
rawKeyPair: "good-keypair",
|
||||
},
|
||||
cert: {
|
||||
validUntil: Date.now() + CERT_LIFETIME + 10000,
|
||||
rawCert: "good-cert",
|
||||
},
|
||||
};
|
||||
|
||||
yield fxa.setSignedInUser(credentials);
|
||||
let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState);
|
||||
// should have the same keypair and cert.
|
||||
do_check_eq(keyPair, credentials.keyPair.rawKeyPair);
|
||||
do_check_eq(certificate, credentials.cert.rawCert);
|
||||
yield fxa.signOut(/*localOnly = */true);
|
||||
});
|
||||
|
||||
add_task(function* test_getCertificateExpiredCert() {
|
||||
_("getCertificateExpiredCert()");
|
||||
let fxa = MakeFxAccounts({
|
||||
getCertificateSigned() {
|
||||
return "new cert";
|
||||
}
|
||||
});
|
||||
let credentials = {
|
||||
email: "foo@example.com",
|
||||
uid: "1234@lcip.org",
|
||||
sessionToken: "dead",
|
||||
verified: true,
|
||||
// A cached keypair that remains valid.
|
||||
keyPair: {
|
||||
validUntil: Date.now() + KEY_LIFETIME + 10000,
|
||||
rawKeyPair: "good-keypair",
|
||||
},
|
||||
// A cached certificate which has expired.
|
||||
cert: {
|
||||
validUntil: Date.parse("Mon, 13 Jan 2000 21:45:06 GMT"),
|
||||
rawCert: "expired-cert",
|
||||
},
|
||||
};
|
||||
yield fxa.setSignedInUser(credentials);
|
||||
let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState);
|
||||
// should have the same keypair but a new cert.
|
||||
do_check_eq(keyPair, credentials.keyPair.rawKeyPair);
|
||||
do_check_neq(certificate, credentials.cert.rawCert);
|
||||
yield fxa.signOut(/*localOnly = */true);
|
||||
});
|
||||
|
||||
add_task(function* test_getCertificateExpiredKeypair() {
|
||||
_("getCertificateExpiredKeypair()");
|
||||
let fxa = MakeFxAccounts({
|
||||
getCertificateSigned() {
|
||||
return "new cert";
|
||||
},
|
||||
});
|
||||
let credentials = {
|
||||
email: "foo@example.com",
|
||||
uid: "1234@lcip.org",
|
||||
sessionToken: "dead",
|
||||
verified: true,
|
||||
// A cached keypair that has expired.
|
||||
keyPair: {
|
||||
validUntil: Date.now() - 1000,
|
||||
rawKeyPair: "expired-keypair",
|
||||
},
|
||||
// A cached certificate which remains valid.
|
||||
cert: {
|
||||
validUntil: Date.now() + CERT_LIFETIME + 10000,
|
||||
rawCert: "expired-cert",
|
||||
},
|
||||
};
|
||||
|
||||
yield fxa.setSignedInUser(credentials);
|
||||
let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState);
|
||||
// even though the cert was valid, the fact the keypair was not means we
|
||||
// should have fetched both.
|
||||
do_check_neq(keyPair, credentials.keyPair.rawKeyPair);
|
||||
do_check_neq(certificate, credentials.cert.rawCert);
|
||||
yield fxa.signOut(/*localOnly = */true);
|
||||
});
|
||||
|
||||
// Sanity-check that our mocked client is working correctly
|
||||
add_test(function test_client_mock() {
|
||||
|
|
|
@ -184,14 +184,10 @@ this.configureFxAccountIdentity = function(authService,
|
|||
let accountState = new AccountState(storageManager);
|
||||
return accountState;
|
||||
},
|
||||
getCertificate(data, keyPair, mustBeValidUntil) {
|
||||
let cert = {
|
||||
validUntil: this.now() + CERT_LIFETIME,
|
||||
cert: "certificate",
|
||||
};
|
||||
this.currentAccountState.updateUserAccountData({cert: cert});
|
||||
return Promise.resolve(cert.cert);
|
||||
_getAssertion(audience) {
|
||||
return Promise.resolve("assertion");
|
||||
},
|
||||
|
||||
};
|
||||
fxa = new FxAccounts(MockInternal);
|
||||
|
||||
|
|
|
@ -78,6 +78,12 @@ var signonsTreeView = {
|
|||
return "";
|
||||
}
|
||||
},
|
||||
isEditable : function(row, col) {
|
||||
if (col.id == "userCol" || col.id == "passwordCol") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isSeparator : function(index) { return false; },
|
||||
isSorted : function() { return false; },
|
||||
isContainer : function(index) { return false; },
|
||||
|
@ -89,7 +95,31 @@ var signonsTreeView = {
|
|||
return "ltr";
|
||||
|
||||
return "";
|
||||
}
|
||||
},
|
||||
setCellText : function(row, col, value) {
|
||||
// If there is a filter, _filterSet needs to be used, otherwise signons is used.
|
||||
let table = signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons;
|
||||
function _editLogin(field) {
|
||||
if (value == table[row][field]) {
|
||||
return;
|
||||
}
|
||||
let existingLogin = table[row].clone();
|
||||
table[row][field] = value;
|
||||
table[row].timePasswordChanged = Date.now();
|
||||
passwordmanager.modifyLogin(existingLogin, table[row]);
|
||||
signonsTree.treeBoxObject.invalidateRow(row);
|
||||
}
|
||||
|
||||
if (col.id == "userCol") {
|
||||
_editLogin("username");
|
||||
|
||||
} else if (col.id == "passwordCol") {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
_editLogin("password");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
@ -207,11 +237,15 @@ function FinalizeSignonDeletions(syncNeeded) {
|
|||
}
|
||||
|
||||
function HandleSignonKeyPress(e) {
|
||||
// If editing is currently performed, don't do anything.
|
||||
if (signonsTree.getAttribute("editing")) {
|
||||
return;
|
||||
}
|
||||
if (e.keyCode == KeyEvent.DOM_VK_DELETE
|
||||
#ifdef XP_MACOSX
|
||||
|| e.keyCode == KeyEvent.DOM_VK_BACK_SPACE
|
||||
#endif
|
||||
) {
|
||||
) {
|
||||
DeleteSignon();
|
||||
}
|
||||
}
|
||||
|
@ -412,3 +446,11 @@ function masterPasswordLogin(noPasswordCallback) {
|
|||
|
||||
return token.isLoggedIn();
|
||||
}
|
||||
|
||||
function escapeKeyHandler() {
|
||||
// If editing is currently performed, don't do anything.
|
||||
if (signonsTree.getAttribute("editing")) {
|
||||
return;
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<keyset>
|
||||
<key keycode="VK_ESCAPE" oncommand="window.close();"/>
|
||||
<key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
|
||||
<key key="&windowClose.key;" modifiers="accel" oncommand="escapeKeyHandler();"/>
|
||||
<key key="&focusSearch1.key;" modifiers="accel" oncommand="FocusFilterBox();"/>
|
||||
<key key="&focusSearch2.key;" modifiers="accel" oncommand="FocusFilterBox();"/>
|
||||
</keyset>
|
||||
|
@ -60,6 +60,7 @@
|
|||
style="height: 20em;"
|
||||
onkeypress="HandleSignonKeyPress(event)"
|
||||
onselect="SignonSelected();"
|
||||
editable="true"
|
||||
context="signonsTreeContextMenu">
|
||||
<treecols>
|
||||
<treecol id="siteCol" label="&treehead.site.label;" flex="40"
|
||||
|
|
|
@ -8,6 +8,8 @@ support-files =
|
|||
[browser_filldoorhanger.js]
|
||||
[browser_notifications.js]
|
||||
skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
|
||||
[browser_passwordmgr_editing.js]
|
||||
skip-if = os == "linux"
|
||||
[browser_passwordmgr_fields.js]
|
||||
[browser_passwordmgr_observers.js]
|
||||
[browser_passwordmgr_sort.js]
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
const { ContentTaskUtils } = Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
|
||||
const TIME_INTERVAL = 500;
|
||||
const PWMGR_DLG = "chrome://passwordmgr/content/passwordManager.xul";
|
||||
|
||||
let doc;
|
||||
let pwmgr;
|
||||
let pwmgrdlg;
|
||||
let signonsTree;
|
||||
|
||||
function addLogin(site, username, password) {
|
||||
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
|
||||
Ci.nsILoginInfo, "init");
|
||||
let login = new nsLoginInfo(site, site, null, username, password, "u", "p");
|
||||
Services.logins.addLogin(login);
|
||||
}
|
||||
|
||||
function getUsername(row) {
|
||||
return signonsTree.view.getCellText(row, signonsTree.columns.getNamedColumn("userCol"));
|
||||
}
|
||||
|
||||
function getPassword(row) {
|
||||
return signonsTree.view.getCellText(row, signonsTree.columns.getNamedColumn("passwordCol"));
|
||||
}
|
||||
|
||||
function synthesizeDblClickOnCell(aTree, column, row) {
|
||||
let tbo = aTree.treeBoxObject;
|
||||
let rect = tbo.getCoordsForCellItem(row, aTree.columns[column], "text");
|
||||
let x = rect.x + rect.width / 2;
|
||||
let y = rect.y + rect.height / 2;
|
||||
// Simulate the double click.
|
||||
EventUtils.synthesizeMouse(aTree.body, x, y, { clickCount: 2 },
|
||||
aTree.ownerDocument.defaultView);
|
||||
}
|
||||
|
||||
function togglePasswords() {
|
||||
pwmgrdlg.document.querySelector("#togglePasswords").doCommand();
|
||||
}
|
||||
|
||||
function* editUsernamePromises(site, oldUsername, newUsername) {
|
||||
is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login found");
|
||||
let login = Services.logins.findLogins({}, site, "", "")[0];
|
||||
is(login.username, oldUsername, "Correct username saved");
|
||||
is(getUsername(0), oldUsername, "Correct username shown");
|
||||
synthesizeDblClickOnCell(signonsTree, 1, 0);
|
||||
yield ContentTaskUtils.waitForCondition(() => signonsTree.getAttribute("editing"),
|
||||
"Waiting for editing");
|
||||
|
||||
EventUtils.sendString(newUsername, pwmgrdlg);
|
||||
let signonsIntro = doc.querySelector("#signonsIntro");
|
||||
EventUtils.sendMouseEvent({type: "click"}, signonsIntro, pwmgrdlg);
|
||||
yield ContentTaskUtils.waitForCondition(() => !signonsTree.getAttribute("editing"),
|
||||
"Waiting for editing to stop");
|
||||
|
||||
is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login replaced");
|
||||
login = Services.logins.findLogins({}, site, "", "")[0];
|
||||
is(login.username, newUsername, "Correct username updated");
|
||||
is(getUsername(0), newUsername, "Correct username shown");
|
||||
}
|
||||
|
||||
function* editPasswordPromises(site, oldPassword, newPassword) {
|
||||
is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login found");
|
||||
let login = Services.logins.findLogins({}, site, "", "")[0];
|
||||
is(login.password, oldPassword, "Correct password saved");
|
||||
is(getPassword(0), oldPassword, "Correct password shown");
|
||||
|
||||
synthesizeDblClickOnCell(signonsTree, 2, 0);
|
||||
yield ContentTaskUtils.waitForCondition(() => signonsTree.getAttribute("editing"),
|
||||
"Waiting for editing");
|
||||
|
||||
EventUtils.sendString(newPassword, pwmgrdlg);
|
||||
let signonsIntro = doc.querySelector("#signonsIntro");
|
||||
EventUtils.sendMouseEvent({type: "click"}, signonsIntro, pwmgrdlg);
|
||||
yield ContentTaskUtils.waitForCondition(() => !signonsTree.getAttribute("editing"),
|
||||
"Waiting for editing to stop");
|
||||
|
||||
is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login replaced");
|
||||
login = Services.logins.findLogins({}, site, "", "")[0];
|
||||
is(login.password, newPassword, "Correct password updated");
|
||||
is(getPassword(0), newPassword, "Correct password shown");
|
||||
}
|
||||
|
||||
add_task(function* test_setup() {
|
||||
registerCleanupFunction(function() {
|
||||
Services.logins.removeAllLogins();
|
||||
});
|
||||
|
||||
Services.logins.removeAllLogins();
|
||||
// Open the password manager dialog.
|
||||
pwmgrdlg = window.openDialog(PWMGR_DLG, "Toolkit:PasswordManager", "");
|
||||
|
||||
Services.ww.registerNotification(function (aSubject, aTopic, aData) {
|
||||
if (aTopic == "domwindowopened") {
|
||||
let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
|
||||
SimpleTest.waitForFocus(function() {
|
||||
EventUtils.sendKey("RETURN", win);
|
||||
}, win);
|
||||
} else if (aSubject.location == pwmgrdlg.location && aTopic == "domwindowclosed") {
|
||||
// Unregister ourself.
|
||||
Services.ww.unregisterNotification(arguments.callee);
|
||||
}
|
||||
});
|
||||
|
||||
yield new Promise((resolve) => {
|
||||
SimpleTest.waitForFocus(() => {
|
||||
doc = pwmgrdlg.document;
|
||||
signonsTree = doc.querySelector("#signonsTree");
|
||||
resolve();
|
||||
}, pwmgrdlg);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* test_edit_multiple_logins() {
|
||||
function* testLoginChange(site, oldUsername, oldPassword, newUsername, newPassword) {
|
||||
addLogin(site, oldUsername, oldPassword);
|
||||
yield* editUsernamePromises(site, oldUsername, newUsername);
|
||||
togglePasswords();
|
||||
yield* editPasswordPromises(site, oldPassword, newPassword);
|
||||
togglePasswords();
|
||||
}
|
||||
|
||||
yield* testLoginChange("http://c.tn/", "userC", "passC", "usernameC", "passwordC");
|
||||
yield* testLoginChange("http://b.tn/", "userB", "passB", "usernameB", "passwordB");
|
||||
yield* testLoginChange("http://a.tn/", "userA", "passA", "usernameA", "passwordA");
|
||||
|
||||
pwmgrdlg.close();
|
||||
});
|
|
@ -162,11 +162,21 @@ function generateUUID() {
|
|||
return str.substring(1, str.length - 1);
|
||||
}
|
||||
|
||||
function getMsSinceProcessStart() {
|
||||
try {
|
||||
return Telemetry.msSinceProcessStart();
|
||||
} catch (ex) {
|
||||
// If this fails return a special value.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a policy object used to override behavior for testing.
|
||||
*/
|
||||
let Policy = {
|
||||
now: () => new Date(),
|
||||
monotonicNow: getMsSinceProcessStart,
|
||||
generateSessionUUID: () => generateUUID(),
|
||||
generateSubsessionUUID: () => generateUUID(),
|
||||
setSchedulerTickTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
||||
|
@ -710,6 +720,7 @@ this.TelemetrySession = Object.freeze({
|
|||
Impl._subsessionCounter = 0;
|
||||
Impl._profileSubsessionCounter = 0;
|
||||
Impl._subsessionStartActiveTicks = 0;
|
||||
Impl._subsessionStartTimeMonotonic = 0;
|
||||
this.uninstall();
|
||||
return this.setup();
|
||||
},
|
||||
|
@ -795,6 +806,9 @@ let Impl = {
|
|||
_profileSubsessionCounter: 0,
|
||||
// Date of the last session split
|
||||
_subsessionStartDate: null,
|
||||
// Start time of the current subsession using a monotonic clock for the subsession
|
||||
// length measurements.
|
||||
_subsessionStartTimeMonotonic: 0,
|
||||
// The active ticks counted when the subsession starts
|
||||
_subsessionStartActiveTicks: 0,
|
||||
// A task performing delayed initialization of the chrome process
|
||||
|
@ -1079,11 +1093,9 @@ let Impl = {
|
|||
getMetadata: function getMetadata(reason) {
|
||||
this._log.trace("getMetadata - Reason " + reason);
|
||||
|
||||
let sessionStartDate = toLocalTimeISOString(Utils.truncateToDays(this._sessionStartDate));
|
||||
let subsessionStartDate = toLocalTimeISOString(Utils.truncateToDays(this._subsessionStartDate));
|
||||
// Compute the subsession length in milliseconds, then convert to seconds.
|
||||
let subsessionLength =
|
||||
Math.floor((Policy.now() - this._subsessionStartDate.getTime()) / 1000);
|
||||
const sessionStartDate = toLocalTimeISOString(Utils.truncateToDays(this._sessionStartDate));
|
||||
const subsessionStartDate = toLocalTimeISOString(Utils.truncateToDays(this._subsessionStartDate));
|
||||
const monotonicNow = Policy.monotonicNow();
|
||||
|
||||
let ret = {
|
||||
reason: reason,
|
||||
|
@ -1105,7 +1117,13 @@ let Impl = {
|
|||
|
||||
sessionStartDate: sessionStartDate,
|
||||
subsessionStartDate: subsessionStartDate,
|
||||
subsessionLength: subsessionLength,
|
||||
|
||||
// Compute the session and subsession length in seconds.
|
||||
// We use monotonic clocks as Date() is affected by jumping clocks (leading
|
||||
// to negative lengths and other issues).
|
||||
sessionLength: Math.floor(monotonicNow / 1000),
|
||||
subsessionLength:
|
||||
Math.floor((monotonicNow - this._subsessionStartTimeMonotonic) / 1000),
|
||||
};
|
||||
|
||||
// TODO: Remove this when bug 1124128 lands.
|
||||
|
@ -1334,6 +1352,7 @@ let Impl = {
|
|||
*/
|
||||
startNewSubsession: function () {
|
||||
this._subsessionStartDate = Policy.now();
|
||||
this._subsessionStartTimeMonotonic = Policy.monotonicNow();
|
||||
this._previousSubsessionId = this._subsessionId;
|
||||
this._subsessionId = Policy.generateSubsessionUUID();
|
||||
this._subsessionCounter++;
|
||||
|
|
|
@ -39,7 +39,8 @@ Structure::
|
|||
|
||||
sessionStartDate: <ISO date>, // daily precision
|
||||
subsessionStartDate: <ISO date>, // daily precision, ISO date in local time
|
||||
subsessionLength: <number>, // the subsession length in seconds
|
||||
sessionLength: <number>, // the session length until now in seconds, monotonic
|
||||
subsessionLength: <number>, // the subsession length in seconds, monotonic
|
||||
},
|
||||
|
||||
childPayloads: {...}, // only present with e10s; a reduced payload from content processes
|
||||
|
@ -58,3 +59,21 @@ Structure::
|
|||
slowSQL: {...},
|
||||
slowSQLstartup: {...},
|
||||
}
|
||||
|
||||
info
|
||||
----
|
||||
|
||||
sessionLength
|
||||
~~~~~~~~~~~~~
|
||||
The length of the current session so far in seconds.
|
||||
This uses a monotonic clock, so this may mismatch with other measurements that
|
||||
are not monotonic like calculations based on ``Date.now()``.
|
||||
|
||||
If the monotonic clock failed, this will be ``-1``.
|
||||
|
||||
subsessionLength
|
||||
~~~~~~~~~~~~~~~~
|
||||
The length of this subsession in seconds.
|
||||
This uses a monotonic clock, so this may mismatch with other measurements that are not monotonic (e.g. based on Date.now()).
|
||||
|
||||
If ``sessionLength`` is ``-1``, the monotonic clock is not working.
|
||||
|
|
|
@ -248,6 +248,12 @@ function fakeNow(...args) {
|
|||
return new Date(date);
|
||||
}
|
||||
|
||||
function fakeMonotonicNow(ms) {
|
||||
const m = Cu.import("resource://gre/modules/TelemetrySession.jsm");
|
||||
m.Policy.monotonicNow = () => ms;
|
||||
return ms;
|
||||
}
|
||||
|
||||
// Fake the timeout functions for TelemetryController sending.
|
||||
function fakePingSendTimer(set, clear) {
|
||||
let module = Cu.import("resource://gre/modules/TelemetrySend.jsm");
|
||||
|
|
|
@ -519,6 +519,7 @@ add_task(function* test_simplePing() {
|
|||
let now = new Date(2020, 1, 1, 12, 0, 0);
|
||||
let expectedDate = new Date(2020, 1, 1, 0, 0, 0);
|
||||
fakeNow(now);
|
||||
const monotonicStart = fakeMonotonicNow(5000);
|
||||
|
||||
const expectedSessionUUID = "bd314d15-95bf-4356-b682-b6c4a8942202";
|
||||
const expectedSubsessionUUID = "3e2e5f6c-74ba-4e4d-a93f-a48af238a8c7";
|
||||
|
@ -529,6 +530,7 @@ add_task(function* test_simplePing() {
|
|||
// now fake the session duration.
|
||||
const SESSION_DURATION_IN_MINUTES = 15;
|
||||
fakeNow(new Date(2020, 1, 1, 12, SESSION_DURATION_IN_MINUTES, 0));
|
||||
fakeMonotonicNow(monotonicStart + SESSION_DURATION_IN_MINUTES * 60 * 1000);
|
||||
|
||||
yield sendPing();
|
||||
let ping = yield PingServer.promiseNextPing();
|
||||
|
|
|
@ -80,7 +80,7 @@ function forbidCPOW(arg, func, argname)
|
|||
// - A linked document using Alt-click Save Link As...
|
||||
//
|
||||
function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
|
||||
aSkipPrompt, aReferrer, aSourceDocument)
|
||||
aSkipPrompt, aReferrer, aSourceDocument, aIsContentWindowPrivate)
|
||||
{
|
||||
forbidCPOW(aURL, "saveURL", "aURL");
|
||||
forbidCPOW(aReferrer, "saveURL", "aReferrer");
|
||||
|
@ -88,7 +88,7 @@ function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
|
|||
|
||||
internalSave(aURL, null, aFileName, null, null, aShouldBypassCache,
|
||||
aFilePickerTitleKey, null, aReferrer, aSourceDocument,
|
||||
aSkipPrompt, null);
|
||||
aSkipPrompt, null, aIsContentWindowPrivate);
|
||||
}
|
||||
|
||||
// Just like saveURL, but will get some info off the image before
|
||||
|
@ -264,19 +264,24 @@ const kSaveAsType_Text = 2; // Save document, converting to plain text.
|
|||
* @param aReferrer
|
||||
* the referrer URI object (not URL string) to use, or null
|
||||
* if no referrer should be sent.
|
||||
* @param aInitiatingDocument
|
||||
* @param aInitiatingDocument [optional]
|
||||
* The document from which the save was initiated.
|
||||
* If this is omitted then aIsContentWindowPrivate has to be provided.
|
||||
* @param aSkipPrompt [optional]
|
||||
* If set to true, we will attempt to save the file to the
|
||||
* default downloads folder without prompting.
|
||||
* @param aCacheKey [optional]
|
||||
* If set will be passed to saveURI. See nsIWebBrowserPersist for
|
||||
* allowed values.
|
||||
* @param aIsContentWindowPrivate [optional]
|
||||
* This parameter is provided when the aInitiatingDocument is not a
|
||||
* real document object. Stores whether aInitiatingDocument.defaultView
|
||||
* was private or not.
|
||||
*/
|
||||
function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
|
||||
aContentType, aShouldBypassCache, aFilePickerTitleKey,
|
||||
aChosenData, aReferrer, aInitiatingDocument, aSkipPrompt,
|
||||
aCacheKey)
|
||||
aCacheKey, aIsContentWindowPrivate)
|
||||
{
|
||||
forbidCPOW(aURL, "internalSave", "aURL");
|
||||
forbidCPOW(aReferrer, "internalSave", "aReferrer");
|
||||
|
@ -357,7 +362,8 @@ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
|
|||
sourceCacheKey : aCacheKey,
|
||||
sourcePostData : nonCPOWDocument ? getPostData(aDocument) : null,
|
||||
bypassCache : aShouldBypassCache,
|
||||
initiatingWindow : aInitiatingDocument.defaultView
|
||||
initiatingWindow : aInitiatingDocument && aInitiatingDocument.defaultView,
|
||||
isContentWindowPrivate : aIsContentWindowPrivate
|
||||
};
|
||||
|
||||
// Start the actual save process
|
||||
|
@ -392,8 +398,12 @@ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
|
|||
* "text/plain" is meaningful.
|
||||
* @param persistArgs.bypassCache
|
||||
* If true, the document will always be refetched from the server
|
||||
* @param persistArgs.initiatingWindow
|
||||
* @param persistArgs.initiatingWindow [optional]
|
||||
* The window from which the save operation was initiated.
|
||||
* If this is omitted then isContentWindowPrivate has to be provided.
|
||||
* @param persistArgs.isContentWindowPrivate [optional]
|
||||
* If present then isPrivate is set to this value without using
|
||||
* persistArgs.initiatingWindow.
|
||||
*/
|
||||
function internalPersist(persistArgs)
|
||||
{
|
||||
|
@ -414,7 +424,10 @@ function internalPersist(persistArgs)
|
|||
// Find the URI associated with the target file
|
||||
var targetFileURL = makeFileURI(persistArgs.targetFile);
|
||||
|
||||
let isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(persistArgs.initiatingWindow);
|
||||
let isPrivate = persistArgs.isContentWindowPrivate;
|
||||
if (isPrivate === undefined) {
|
||||
isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(persistArgs.initiatingWindow);
|
||||
}
|
||||
|
||||
// Create download and initiate it (below)
|
||||
var tr = Components.classes["@mozilla.org/transfer;1"].createInstance(Components.interfaces.nsITransfer);
|
||||
|
|
|
@ -44,12 +44,13 @@ loader.lazyGetter(this, "DOMUtils", function() {
|
|||
* Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
* const {OutputParser} = devtools.require("devtools/output-parser");
|
||||
*
|
||||
* let parser = new OutputParser();
|
||||
* let parser = new OutputParser(document);
|
||||
*
|
||||
* parser.parseCssProperty("color", "red"); // Returns document fragment.
|
||||
*/
|
||||
function OutputParser() {
|
||||
function OutputParser(document) {
|
||||
this.parsed = [];
|
||||
this.doc = document;
|
||||
this.colorSwatches = new WeakMap();
|
||||
this._onSwatchMouseDown = this._onSwatchMouseDown.bind(this);
|
||||
}
|
||||
|
@ -442,12 +443,10 @@ OutputParser.prototype = {
|
|||
* @param {String} [value]
|
||||
* If a value is included it will be appended as a text node inside
|
||||
* the tag. This is useful e.g. for span tags.
|
||||
* @return {Node} Newly created Node.
|
||||
* @return {Node} Newly created Node.
|
||||
*/
|
||||
_createNode: function(tagName, attributes, value="") {
|
||||
let win = Services.appShell.hiddenDOMWindow;
|
||||
let doc = win.document;
|
||||
let node = doc.createElementNS(HTML_NS, tagName);
|
||||
let node = this.doc.createElementNS(HTML_NS, tagName);
|
||||
let attrs = Object.getOwnPropertyNames(attributes);
|
||||
|
||||
for (let attr of attrs) {
|
||||
|
@ -457,7 +456,7 @@ OutputParser.prototype = {
|
|||
}
|
||||
|
||||
if (value) {
|
||||
let textNode = doc.createTextNode(value);
|
||||
let textNode = this.doc.createTextNode(value);
|
||||
node.appendChild(textNode);
|
||||
}
|
||||
|
||||
|
@ -503,13 +502,11 @@ OutputParser.prototype = {
|
|||
* Document Fragment
|
||||
*/
|
||||
_toDOM: function() {
|
||||
let win = Services.appShell.hiddenDOMWindow;
|
||||
let doc = win.document;
|
||||
let frag = doc.createDocumentFragment();
|
||||
let frag = this.doc.createDocumentFragment();
|
||||
|
||||
for (let item of this.parsed) {
|
||||
if (typeof item === "string") {
|
||||
frag.appendChild(doc.createTextNode(item));
|
||||
frag.appendChild(this.doc.createTextNode(item));
|
||||
} else {
|
||||
frag.appendChild(item);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче