diff options
Diffstat (limited to 'comm/suite/browser/content.js')
-rw-r--r-- | comm/suite/browser/content.js | 901 |
1 files changed, 901 insertions, 0 deletions
diff --git a/comm/suite/browser/content.js b/comm/suite/browser/content.js new file mode 100644 index 0000000000..40dd01d7c2 --- /dev/null +++ b/comm/suite/browser/content.js @@ -0,0 +1,901 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* 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/. */ + +/* This content script should work in any browser or iframe and should not + * depend on the frame being contained in tabbrowser. */ + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +ChromeUtils.defineModuleGetter(this, "LoginManagerContent", + "resource://gre/modules/LoginManagerContent.jsm"); +ChromeUtils.defineModuleGetter(this, "InsecurePasswordUtils", + "resource://gre/modules/InsecurePasswordUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "LoginFormFactory", + "resource://gre/modules/LoginManagerContent.jsm"); +ChromeUtils.defineModuleGetter(this, "PlacesUIUtils", + "resource:///modules/PlacesUIUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "setTimeout", + "resource://gre/modules/Timer.jsm"); +ChromeUtils.defineModuleGetter(this, "Feeds", + "resource:///modules/Feeds.jsm"); +ChromeUtils.defineModuleGetter(this, "BrowserUtils", + "resource://gre/modules/BrowserUtils.jsm"); + +XPCOMUtils.defineLazyGetter(this, "gPipNSSBundle", function() { + return Services.strings.createBundle("chrome://pipnss/locale/pipnss.properties"); +}); +XPCOMUtils.defineLazyGetter(this, "gNSSErrorsBundle", function() { + return Services.strings.createBundle("chrome://pipnss/locale/nsserrors.properties"); +}); + +addMessageListener("RemoteLogins:fillForm", message => { + LoginManagerContent.receiveMessage(message, content); +}); + +addEventListener("DOMFormHasPassword", event => { + LoginManagerContent.onDOMFormHasPassword(event, content); + let formLike = LoginFormFactory.createFromForm(event.target); + InsecurePasswordUtils.reportInsecurePasswords(formLike); +}); + +addEventListener("DOMInputPasswordAdded", event => { + LoginManagerContent.onDOMInputPasswordAdded(event, content); + let formLike = LoginFormFactory.createFromField(event.target); + InsecurePasswordUtils.reportInsecurePasswords(formLike); +}); + +addEventListener("pageshow", event => { + LoginManagerContent.onPageShow(event, content); +}, true); + +addEventListener("DOMAutoComplete", event => { + LoginManagerContent.onUsernameInput(event); +}); + +addEventListener("blur", event => { + LoginManagerContent.onUsernameInput(event); +}); + +addMessageListener("Bookmarks:GetPageDetails", (message) => { + let doc = content.document; + let isErrorPage = /^about:(neterror|certerror|blocked)/.test(doc.documentURI); + sendAsyncMessage("Bookmarks:GetPageDetails:Result", + { isErrorPage, + description: PlacesUIUtils.getDescriptionFromDocument(doc) }); +}); + +/* The following code, in particular AboutCertErrorListener and + * AboutNetErrorListener, is mostly copied from content browser.js and content.js. + * Certificate error handling should be unified to remove this duplicated code. + */ + +const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE; +const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE; + +const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11; +const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13; +const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20; +const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21; +const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30; +const SEC_ERROR_CA_CERT_INVALID = SEC_ERROR_BASE + 36; +const SEC_ERROR_OCSP_FUTURE_RESPONSE = SEC_ERROR_BASE + 131; +const SEC_ERROR_OCSP_OLD_RESPONSE = SEC_ERROR_BASE + 132; +const SEC_ERROR_REUSED_ISSUER_AND_SERIAL = SEC_ERROR_BASE + 138; +const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176; +const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5; +const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6; +const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = MOZILLA_PKIX_ERROR_BASE + 14; +const MOZILLA_PKIX_ERROR_MITM_DETECTED = MOZILLA_PKIX_ERROR_BASE + 15; + + +const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE; +const SSL_ERROR_SSL_DISABLED = SSL_ERROR_BASE + 20; +const SSL_ERROR_SSL2_DISABLED = SSL_ERROR_BASE + 14; + +var AboutNetAndCertErrorListener = { + init(chromeGlobal) { + addEventListener("AboutNetAndCertErrorLoad", this, false, true); + }, + + get isNetErrorSite() { + return content.document.documentURI.startsWith("about:neterror"); + }, + + get isCertErrorSite() { + return content.document.documentURI.startsWith("about:certerror"); + }, + + _getErrorMessageFromCode(securityInfo, doc) { + let uri = Services.io.newURI(doc.location); + let hostString = uri.host; + if (uri.port != 443 && uri.port != -1) { + hostString += ":" + uri.port; + } + + let id_str = ""; + switch (securityInfo.errorCode) { + case SSL_ERROR_SSL_DISABLED: + id_str = "PSMERR_SSL_Disabled"; + break; + case SSL_ERROR_SSL2_DISABLED: + id_str = "PSMERR_SSL2_Disabled"; + break; + case SEC_ERROR_REUSED_ISSUER_AND_SERIAL: + id_str = "PSMERR_HostReusedIssuerSerial"; + break; + } + let nss_error_id_str = securityInfo.errorCodeString; + let msg2 = ""; + if (id_str) { + msg2 = gPipNSSBundle.GetStringFromName(id_str) + "\n"; + } else if (nss_error_id_str) { + msg2 = gNSSErrorsBundle.GetStringFromName(nss_error_id_str) + "\n"; + } + + if (!msg2) { + // We couldn't get an error message. Use the error string. + // Note that this is different from before where we used PR_ErrorToString. + msg2 = nss_error_id_str; + } + let msg = gPipNSSBundle.formatStringFromName("SSLConnectionErrorPrefix2", + [hostString, msg2], 2); + + if (nss_error_id_str) { + msg += gPipNSSBundle.formatStringFromName("certErrorCodePrefix3", + [nss_error_id_str], 1); + } + let id = content.document.getElementById("errorShortDescText"); + id.textContent = msg; + id.className = "wrap"; + }, + + _setTechDetails(sslStatus, securityInfo, location) { + if (!securityInfo || !sslStatus || !location) { + return; + } + let validity = sslStatus.serverCert.validity; + + let doc = content.document; + // CSS class and error code are set from nsDocShell. + let searchParams = new URLSearchParams(doc.documentURI.split("?")[1]); + let cssClass = searchParams.get("s"); + let error = searchParams.get("e"); + let technicalInfo = doc.getElementById("technicalContentText"); + + let uri = Services.io.newURI(location); + let hostString = uri.host; + if (uri.port != 443 && uri.port != -1) { + hostString += ":" + uri.port; + } + + let msg = gPipNSSBundle.formatStringFromName("certErrorIntro", + [hostString], 1); + msg += "\n\n"; + + if (sslStatus.isUntrusted) { + switch (securityInfo.errorCode) { + case MOZILLA_PKIX_ERROR_MITM_DETECTED: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_MitM") + "\n"; + break; + case SEC_ERROR_UNKNOWN_ISSUER: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer") + "\n"; + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer2") + "\n"; + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer3") + "\n"; + break; + case SEC_ERROR_CA_CERT_INVALID: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_CaInvalid") + "\n"; + break; + case SEC_ERROR_UNTRUSTED_ISSUER: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_Issuer") + "\n"; + break; + case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_SignatureAlgorithmDisabled") + "\n"; + break; + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_ExpiredIssuer") + "\n"; + break; + case MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_SelfSigned") + "\n"; + break; + case SEC_ERROR_UNTRUSTED_CERT: + default: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_Untrusted") + "\n"; + } + } + + technicalInfo.appendChild(doc.createTextNode(msg)); + + if (sslStatus.isDomainMismatch) { + let subjectAltNamesList = sslStatus.serverCert.subjectAltNames; + let subjectAltNames = subjectAltNamesList.split(","); + let numSubjectAltNames = subjectAltNames.length; + if (numSubjectAltNames != 0) { + if (numSubjectAltNames == 1) { + // Let's check if we want to make this a link. + let okHost = subjectAltNamesList; + let href = ""; + let thisHost = doc.location.hostname; + let proto = doc.location.protocol + "//"; + // If okHost is a wildcard domain ("*.example.com") let's + // use "www" instead. "*.example.com" isn't going to + // get anyone anywhere useful. bug 432491 + okHost = okHost.replace(/^\*\./, "www."); + /* case #1: + * example.com uses an invalid security certificate. + * + * The certificate is only valid for www.example.com + * + * Make sure to include the "." ahead of thisHost so that + * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com" + * + * We'd normally just use a RegExp here except that we lack a + * library function to escape them properly (bug 248062), and + * domain names are famous for having '.' characters in them, + * which would allow spurious and possibly hostile matches. + */ + if (okHost.endsWith("." + thisHost)) { + href = proto + okHost; + } + /* case #2: + * browser.garage.maemo.org uses an invalid security certificate. + * + * The certificate is only valid for garage.maemo.org + */ + if (thisHost.endsWith("." + okHost)) { + href = proto + okHost; + } + + // If we set a link, meaning there's something helpful for + // the user here, expand the section by default + if (href && cssClass != "expertBadCert") { + doc.getElementById("technicalContentText").style.display = "block"; + } + + let msgPrefix = + gPipNSSBundle.GetStringFromName("certErrorMismatchSinglePrefix"); + + // Set the link if we want it. + if (href) { + let referrerlink = doc.createElement("a"); + referrerlink.append(subjectAltNamesList + "\n"); + referrerlink.title = subjectAltNamesList; + referrerlink.id = "cert_domain_link"; + referrerlink.href = href; + msg = BrowserUtils.getLocalizedFragment(doc, msgPrefix, + referrerlink); + } else { + msg = BrowserUtils.getLocalizedFragment(doc, msgPrefix, + subjectAltNamesList); + } + } else { + msg = gPipNSSBundle.GetStringFromName("certErrorMismatchMultiple") + "\n"; + for (let i = 0; i < numSubjectAltNames; i++) { + msg += subjectAltNames[i]; + if (i != (numSubjectAltNames - 1)) { + msg += ", "; + } + } + } + } else { + msg = gPipNSSBundle.formatStringFromName("certErrorMismatch", + [hostString], 1); + } + technicalInfo.append(msg + "\n"); + } + + if (sslStatus.isNotValidAtThisTime) { + let nowTime = new Date().getTime() * 1000; + let dateOptions = { year: "numeric", month: "long", day: "numeric", + hour: "numeric", minute: "numeric" }; + let now = new Services.intl.DateTimeFormat(undefined, dateOptions).format(new Date()); + if (validity.notBefore) { + if (nowTime > validity.notAfter) { + msg = gPipNSSBundle.formatStringFromName("certErrorExpiredNow", + [validity.notAfterLocalTime, now], 2) + "\n"; + } else { + msg = gPipNSSBundle.formatStringFromName("certErrorNotYetValidNow", + [validity.notBeforeLocalTime, now], 2) + "\n"; + } + } else { + // If something goes wrong, we assume the cert expired. + msg = gPipNSSBundle.formatStringFromName("certErrorExpiredNow", + ["", now], 2) + "\n"; + } + technicalInfo.append(msg); + } + technicalInfo.append("\n"); + + // Add link to certificate and error message. + msg = gPipNSSBundle.formatStringFromName("certErrorCodePrefix3", + [securityInfo.errorCodeString], 1); + technicalInfo.append(msg); + }, + + handleEvent(aEvent) { + if (!this.isNetErrorSite && !this.isCertErrorSite) { + return; + } + + if (aEvent.type != "AboutNetAndCertErrorLoad") { + return; + } + + if (this.isNetErrorSite) { + let {securityInfo} = docShell.failedChannel; + // We don't have a securityInfo when this is for example a DNS error. + if (securityInfo) { + securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + this._getErrorMessageFromCode(securityInfo, + aEvent.originalTarget.ownerGlobal); + } + return; + } + + let ownerDoc = aEvent.originalTarget.ownerGlobal; + let securityInfo = docShell.failedChannel && docShell.failedChannel.securityInfo; + securityInfo.QueryInterface(Ci.nsITransportSecurityInfo) + .QueryInterface(Ci.nsISerializable); + let sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider) + .SSLStatus; + this._setTechDetails(sslStatus, securityInfo, ownerDoc.location.href); + }, +}; +AboutNetAndCertErrorListener.init(); + +const MathMLNS = "http://www.w3.org/1998/Math/MathML"; +const XLinkNS = "http://www.w3.org/1999/xlink"; + +let PageInfoListener = { + + init: function() { + addMessageListener("PageInfo:getData", this); + }, + + receiveMessage: function(message) { + let strings = message.data.strings; + let window; + let document; + + let frameOuterWindowID = message.data.frameOuterWindowID; + + // If inside frame then get the frame's window and document. + if (frameOuterWindowID) { + window = Services.wm.getOuterWindowWithId(frameOuterWindowID); + document = window.document; + } + else { + document = content.document; + window = content.window; + } + + let imageElement = message.objects.imageElement; + + let pageInfoData = {metaViewRows: this.getMetaInfo(document), + docInfo: this.getDocumentInfo(document), + feeds: this.getFeedsInfo(document, strings), + windowInfo: this.getWindowInfo(window), + imageInfo: this.getImageInfo(imageElement)}; + + sendAsyncMessage("PageInfo:data", pageInfoData); + + // Separate step so page info dialog isn't blank while waiting for this + // to finish. + this.getMediaInfo(document, window, strings); + }, + + getImageInfo: function(imageElement) { + let imageInfo = null; + if (imageElement) { + imageInfo = { + currentSrc: imageElement.currentSrc, + width: imageElement.width, + height: imageElement.height, + imageText: imageElement.title || imageElement.alt + }; + } + return imageInfo; + }, + + getMetaInfo: function(document) { + let metaViewRows = []; + + // Get the meta tags from the page. + let metaNodes = document.getElementsByTagName("meta"); + + for (let metaNode of metaNodes) { + metaViewRows.push([metaNode.name || metaNode.httpEquiv || + metaNode.getAttribute("property"), + metaNode.content]); + } + + return metaViewRows; + }, + + getWindowInfo: function(window) { + let windowInfo = {}; + windowInfo.isTopWindow = window == window.top; + + let hostName = null; + try { + hostName = window.location.host; + } + catch (exception) { } + + windowInfo.hostName = hostName; + return windowInfo; + }, + + getDocumentInfo: function(document) { + let docInfo = {}; + docInfo.title = document.title; + docInfo.location = document.location.toString(); + docInfo.referrer = document.referrer; + docInfo.compatMode = document.compatMode; + docInfo.contentType = document.contentType; + docInfo.characterSet = document.characterSet; + docInfo.lastModified = document.lastModified; + docInfo.principal = document.nodePrincipal; + + let documentURIObject = {}; + documentURIObject.spec = document.documentURIObject.spec; + docInfo.documentURIObject = documentURIObject; + + docInfo.isContentWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(content); + + return docInfo; + }, + + getFeedsInfo: function(document, strings) { + let feeds = []; + // Get the feeds from the page. + let linkNodes = 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 (let relVal of rel.split(/\s+/)) { + rels[relVal] = true; + } + } + + if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) { + let type = Feeds.isValidFeed(link, document.nodePrincipal, "feed" in rels); + if (type) { + type = strings[type] || 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. + getMediaInfo: function(document, window, strings) + { + let frameList = this.goThroughFrames(document, window); + this.processFrames(document, frameList, strings); + }, + + goThroughFrames: function(document, window) + { + let frameList = [document]; + if (window && window.frames.length > 0) { + let num = window.frames.length; + for (let i = 0; i < num; i++) { + // Recurse through the frames. + frameList = + frameList.concat(this.goThroughFrames(window.frames[i].document, + window.frames[i])); + } + } + return frameList; + }, + + async processFrames(document, frameList, strings) + { + let nodeCount = 0; + for (let doc of frameList) { + let iterator = doc.createTreeWalker(doc, content.NodeFilter.SHOW_ELEMENT); + + // Goes through all the elements on the doc. + while (iterator.nextNode()) { + this.getMediaItems(document, strings, iterator.currentNode); + + if (++nodeCount % 500 == 0) { + // setTimeout every 500 elements so we don't keep blocking the + // content process. + await new Promise(resolve => setTimeout(resolve, 10)); + } + } + } + // Send that page info media fetching has finished. + sendAsyncMessage("PageInfo:mediaData", {isComplete: true}); + }, + + getMediaItems: function(document, strings, elem) + { + // Check for images defined in CSS (e.g. background, borders). + let computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, ""); + // A node can have multiple media items associated with it - for example, + // multiple background images. + let imageItems = []; + let formItems = []; + let linkItems = []; + + let addImage = (url, type, alt, elem, isBg) => { + let element = this.serializeElementInfo(document, url, type, alt, elem, isBg); + imageItems.push([url, type, alt, element, isBg]); + }; + + if (computedStyle) { + let addImgFunc = (label, val) => { + if (val.primitiveType == content.CSSPrimitiveValue.CSS_URI) { + addImage(val.getStringValue(), label, 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, 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(strings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image")); + addImgFunc(strings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source")); + addImgFunc(strings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image")); + addImgFunc(strings.mediaCursor, computedStyle.getPropertyCSSValue("cursor")); + } + + let addForm = (elem) => { + let element = this.serializeFormInfo(document, elem, strings); + formItems.push([elem.name, elem.method, elem.action, element]); + }; + + // One swi^H^H^Hif-else to rule them all. + if (elem instanceof content.HTMLAnchorElement) { + linkItems.push([this.getValueText(elem), elem.href, strings.linkAnchor, + elem.target, elem.accessKey]); + } + else if (elem instanceof content.HTMLImageElement) { + addImage(elem.src, strings.mediaImg, + (elem.hasAttribute("alt")) ? elem.alt : strings.notSet, + elem, false); + } + else if (elem instanceof content.HTMLAreaElement) { + linkItems.push([elem.alt, elem.href, strings.linkArea, elem.target, ""]); + } + else if (elem instanceof content.HTMLVideoElement) { + addImage(elem.currentSrc, strings.mediaVideo, "", elem, false); + } + else if (elem instanceof content.HTMLAudioElement) { + addImage(elem.currentSrc, strings.mediaAudio, "", elem, false); + } + else if (elem instanceof content.HTMLLinkElement) { + if (elem.rel) { + let rel = elem.rel; + if (/\bicon\b/i.test(rel)) { + addImage(elem.href, strings.mediaLink, "", elem, false); + } + else if (/(?:^|\s)stylesheet(?:\s|$)/i.test(rel)) { + linkItems.push([elem.rel, elem.href, strings.linkStylesheet, + elem.target, ""]); + } + else { + linkItems.push([elem.rel, elem.href, strings.linkRel, + elem.target, ""]); + } + } + else { + linkItems.push([elem.rev, elem.href, strings.linkRev, elem.target, ""]); + } + } + else if (elem instanceof content.HTMLInputElement || + elem instanceof content.HTMLButtonElement) { + switch (elem.type.toLowerCase()) { + case "image": + addImage(elem.src, strings.mediaInput, + (elem.hasAttribute("alt")) ? elem.alt : strings.notSet, + elem, false); + // Fall through, <input type="image"> submits, too + case "submit": + if ("form" in elem && elem.form) { + linkItems.push([elem.value || this.getValueText(elem) || + strings.linkSubmit, elem.form.action, + strings.linkSubmission, elem.form.target, ""]); + } + else { + linkItems.push([elem.value || this.getValueText(elem) || + strings.linkSubmit, "", + strings.linkSubmission, "", ""]); + } + } + } + else if (elem instanceof content.HTMLFormElement) { + addForm(elem); + } + else if (elem instanceof content.HTMLObjectElement) { + addImage(elem.data, strings.mediaObject, this.getValueText(elem), + elem, false); + } + else if (elem instanceof content.HTMLEmbedElement) { + addImage(elem.src, strings.mediaEmbed, "", elem, false); + } + else if (elem.namespaceURI == MathMLNS && elem.hasAttribute("href")) { + let href = elem.getAttribute("href"); + try { + href = makeURLAbsolute(elem.baseURI, href, + elem.ownerDocument.characterSet); + } catch (e) {} + linkItems.push([this.getValueText(elem), href, strings.linkX, "", ""]); + } + else if (elem.hasAttributeNS(XLinkNS, "href")) { + let href = elem.getAttributeNS(XLinkNS, "href"); + try { + href = makeURLAbsolute(elem.baseURI, href, + elem.ownerDocument.characterSet); + } catch (e) {} + // SVG images without an xlink:href attribute are ignored + if (elem instanceof content.SVGImageElement) { + addImage(href, strings.mediaImg, "", elem, false); + } + else { + linkItems.push([this.getValueText(elem), href, strings.linkX, "", ""]); + } + } + else if (elem instanceof content.HTMLScriptElement) { + linkItems.push([elem.type || elem.getAttribute("language") || + strings.notSet, elem.src || strings.linkScriptInline, + strings.linkScript, "", "", ""]); + } + if (imageItems.length || formItems.length || linkItems.length) { + sendAsyncMessage("PageInfo:mediaData", + {imageItems, formItems, linkItems, isComplete: false}); + } + }, + + /** + * 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(document, url, type, alt, item, isBG) + { + let result = {}; + + let imageText; + if (!isBG && + !(item instanceof content.SVGImageElement) && + !(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 Ci.nsIImageLoadingContent) { + let imageRequest = + item.getRequest(Ci.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.HTMLEmbedElement = item instanceof content.HTMLEmbedElement; + result.SVGImageElement = item instanceof content.SVGImageElement; + result.HTMLVideoElement = item instanceof content.HTMLVideoElement; + result.HTMLAudioElement = item instanceof content.HTMLAudioElement; + + if (isBG) { + // Items that are showing this image as a background + // image might not necessarily have a width or height, + // so we'll dynamically generate an image and send up the + // natural dimensions. + let img = content.document.createElement("img"); + img.src = url; + result.naturalWidth = img.naturalWidth; + result.naturalHeight = img.naturalHeight; + } else { + // Otherwise, we can use the current width and height + // of the image. + 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; + }, + + serializeFormInfo: function(document, form, strings) + { + let result = {}; + + if (form.name) + result.name = form.name; + + result.encoding = form.encoding; + result.target = form.target; + result.formfields = []; + + function findFirstControl(node, document) { + function FormControlFilter(node) { + if (node instanceof content.HTMLInputElement || + node instanceof content.HTMLSelectElement || + node instanceof content.HTMLButtonElement || + node instanceof content.HTMLTextAreaElement || + node instanceof content.HTMLObjectElement) + return content.NodeFilter.FILTER_ACCEPT; + return content.NodeFilter.FILTER_SKIP; + } + + if (node.hasAttribute("for")) { + return document.getElementById(node.getAttribute("for")); + } + + var iterator = document.createTreeWalker(node, content.NodeFilter.SHOW_ELEMENT, FormControlFilter, true); + + return iterator.nextNode(); + } + + var whatfor; + var labels = []; + for (let label of form.getElementsByTagName("label")) { + var whatfor = findFirstControl(label, document); + + if (whatfor && (whatfor.form == form)) { + labels.push({label: whatfor, labeltext: this.getValueText(label)}); + } + } + + result.formfields = []; + + var val; + for (let formfield of form.elements) { + if (formfield instanceof content.HTMLButtonElement) + val = this.getValueText(formfield); + else + val = (/^password$/i.test(formfield.type)) ? strings.formPassword : formfield.value; + + var fieldlabel = ""; + for (let labelfor of labels) { + if (formfield == labelfor.label) { + fieldlabel = labelfor.labeltext; + } + } + result.formfields.push([fieldlabel, formfield.name, formfield.type, val]); + } + + 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(); |