Bug 1040947 - Opening page info from a remote tab is sluggish. r=florian

This commit is contained in:
Jimmy Wang 2015-07-08 17:18:38 -04:00
parent 23e59a8b07
commit 48054a7edd
11 changed files with 565 additions and 350 deletions

View File

@ -2411,8 +2411,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;

View File

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

View File

@ -1051,7 +1051,8 @@ nsContextMenu.prototype = {
},
viewFrameInfo: function() {
BrowserPageInfo(this.target.ownerDocument);
BrowserPageInfo(this.target.ownerDocument, null, null,
this.frameOuterWindowID);
},
reloadImage: function() {

View File

@ -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");

View File

@ -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();

View File

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

View File

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

View File

@ -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");

View File

@ -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() {

View File

@ -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() {

View File

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