diff options
author | Ruben Rodriguez <ruben@gnu.org> | 2014-10-20 02:24:51 +0200 |
---|---|---|
committer | Ruben Rodriguez <ruben@gnu.org> | 2014-10-20 02:24:51 +0200 |
commit | 6e7918b6ccb69876d339a320091fdee811445395 (patch) | |
tree | 31cb88ee438d652fddefca1193f70289a8b3dcc8 /helpers/DATA/firefox/gnu/extensions/spyblock@gnu.org/lib/contentPolicy.js | |
parent | 60e5b13c35d4d3ba21bb03b026750a0a414f6c77 (diff) |
Generalize data directory
Diffstat (limited to 'helpers/DATA/firefox/gnu/extensions/spyblock@gnu.org/lib/contentPolicy.js')
-rw-r--r-- | helpers/DATA/firefox/gnu/extensions/spyblock@gnu.org/lib/contentPolicy.js | 779 |
1 files changed, 0 insertions, 779 deletions
diff --git a/helpers/DATA/firefox/gnu/extensions/spyblock@gnu.org/lib/contentPolicy.js b/helpers/DATA/firefox/gnu/extensions/spyblock@gnu.org/lib/contentPolicy.js deleted file mode 100644 index 084ebc5..0000000 --- a/helpers/DATA/firefox/gnu/extensions/spyblock@gnu.org/lib/contentPolicy.js +++ /dev/null @@ -1,779 +0,0 @@ -/* - * This file is part of Adblock Plus <http://adblockplus.org/>, - * Copyright (C) 2006-2014 Eyeo GmbH - * - * Adblock Plus is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * Adblock Plus is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * @fileOverview Content policy implementation, responsible for blocking things. - */ - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -let {TimeLine} = require("timeline"); -let {Utils} = require("utils"); -let {Prefs} = require("prefs"); -let {FilterStorage} = require("filterStorage"); -let {BlockingFilter, WhitelistFilter} = require("filterClasses"); -let {defaultMatcher} = require("matcher"); -let {objectMouseEventHander} = require("objectTabs"); -let {RequestNotifier} = require("requestNotifier"); -let {ElemHide} = require("elemHide"); - -/** - * List of explicitly supported content types - * @type Array of String - */ -let contentTypes = ["OTHER", "SCRIPT", "IMAGE", "STYLESHEET", "OBJECT", "SUBDOCUMENT", "DOCUMENT", "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", "MEDIA"]; - -/** - * List of content types that aren't associated with a visual document area - * @type Array of String - */ -let nonVisualTypes = ["SCRIPT", "STYLESHEET", "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT"]; - -/** - * Randomly generated class name, to be applied to collapsed nodes. - */ -let collapsedClass = ""; - -/** - * Public policy checking functions and auxiliary objects - * @class - */ -let Policy = exports.Policy = -{ - /** - * Map of content type identifiers by their name. - * @type Object - */ - type: {}, - - /** - * Map of content type names by their identifiers (reverse of type map). - * @type Object - */ - typeDescr: {}, - - /** - * Map of localized content type names by their identifiers. - * @type Object - */ - localizedDescr: {}, - - /** - * Lists the non-visual content types. - * @type Object - */ - nonVisual: {}, - - /** - * Map containing all schemes that should be ignored by content policy. - * @type Object - */ - whitelistSchemes: {}, - - /** - * Called on module startup, initializes various exported properties. - */ - init: function() - { - TimeLine.enter("Entered content policy initialization"); - - // type constant by type description and type description by type constant - let iface = Ci.nsIContentPolicy; - for (let typeName of contentTypes) - { - if ("TYPE_" + typeName in iface) - { - let id = iface["TYPE_" + typeName]; - this.type[typeName] = id; - this.typeDescr[id] = typeName; - this.localizedDescr[id] = Utils.getString("type_label_" + typeName.toLowerCase()); - } - } - - this.type.ELEMHIDE = 0xFFFD; - this.typeDescr[0xFFFD] = "ELEMHIDE"; - this.localizedDescr[0xFFFD] = Utils.getString("type_label_elemhide"); - - this.type.POPUP = 0xFFFE; - this.typeDescr[0xFFFE] = "POPUP"; - this.localizedDescr[0xFFFE] = Utils.getString("type_label_popup"); - - for (let type of nonVisualTypes) - this.nonVisual[this.type[type]] = true; - - // whitelisted URL schemes - for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" ")) - this.whitelistSchemes[scheme] = true; - - TimeLine.log("done initializing types"); - - // Generate class identifier used to collapse node and register corresponding - // stylesheet. - TimeLine.log("registering global stylesheet"); - - let offset = "a".charCodeAt(0); - for (let i = 0; i < 20; i++) - collapsedClass += String.fromCharCode(offset + Math.random() * 26); - - let collapseStyle = Services.io.newURI("data:text/css," + - encodeURIComponent("." + collapsedClass + - "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarbazdummy) !important;}"), null, null); - Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetService.USER_SHEET); - onShutdown.add(function() - { - Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService.USER_SHEET); - }) - TimeLine.log("done registering stylesheet"); - - TimeLine.leave("Done initializing content policy"); - }, - - /** - * Checks whether a node should be blocked, hides it if necessary - * @param wnd {nsIDOMWindow} - * @param node {nsIDOMElement} - * @param contentType {String} - * @param location {nsIURI} - * @param collapse {Boolean} true to force hiding of the node - * @return {Boolean} false if the node should be blocked - */ - processNode: function(wnd, node, contentType, location, collapse) - { - let topWnd = wnd.top; - if (!topWnd || !topWnd.location || !topWnd.location.href) - return true; - - let privatenode=false; - Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); - if (PrivateBrowsingUtils.isWindowPrivate(wnd)) - privatenode=true; - - let originWindow = Utils.getOriginWindow(wnd); - let wndLocation = originWindow.location.href; - let docDomain = getHostname(wndLocation); - let match = null; - if (!match && Prefs.enabled) - { - let testWnd = wnd; - let parentWndLocation = getWindowLocation(testWnd); - while (true) - { - let testWndLocation = parentWndLocation; - parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : getWindowLocation(testWnd.parent)); - match = Policy.isWhitelisted(testWndLocation, parentWndLocation); - - if (!(match instanceof WhitelistFilter)) - { - let keydata = (testWnd.document && testWnd.document.documentElement ? testWnd.document.documentElement.getAttribute("data-adblockkey") : null); - if (keydata && keydata.indexOf("_") >= 0) - { - let [key, signature] = keydata.split("_", 2); - let keyMatch = defaultMatcher.matchesByKey(testWndLocation, key.replace(/=/g, ""), docDomain); - if (keyMatch && Utils.crypto) - { - // Website specifies a key that we know but is the signature valid? - let uri = Services.io.newURI(testWndLocation, null, null); - let params = [ - uri.path.replace(/#.*/, ""), // REQUEST_URI - uri.asciiHost, // HTTP_HOST - Utils.httpProtocol.userAgent // HTTP_USER_AGENT - ]; - if (Utils.verifySignature(key, signature, params.join("\0"))) - match = keyMatch; - } - } - } - - if (match instanceof WhitelistFilter) - { - FilterStorage.increaseHitCount(match, wnd); - RequestNotifier.addNodeData(testWnd.document, topWnd, Policy.type.DOCUMENT, getHostname(parentWndLocation), false, testWndLocation, match); - return true; - } - - if (testWnd.parent == testWnd) - break; - else - testWnd = testWnd.parent; - } - } - - // Data loaded by plugins should be attached to the document - if (contentType == Policy.type.OBJECT_SUBREQUEST && node instanceof Ci.nsIDOMElement) - node = node.ownerDocument; - - // Fix type for objects misrepresented as frames or images - if (contentType != Policy.type.OBJECT && (node instanceof Ci.nsIDOMHTMLObjectElement || node instanceof Ci.nsIDOMHTMLEmbedElement)) - contentType = Policy.type.OBJECT; - - let locationText = location.spec; - if (!match && contentType == Policy.type.ELEMHIDE) - { - let testWnd = wnd; - let parentWndLocation = getWindowLocation(testWnd); - while (true) - { - let testWndLocation = parentWndLocation; - parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : getWindowLocation(testWnd.parent)); - let parentDocDomain = getHostname(parentWndLocation); - match = defaultMatcher.matchesAny(testWndLocation, "ELEMHIDE", parentDocDomain, false); - if (match instanceof WhitelistFilter) - { - FilterStorage.increaseHitCount(match, wnd); - RequestNotifier.addNodeData(testWnd.document, topWnd, contentType, parentDocDomain, false, testWndLocation, match); - return true; - } - - if (testWnd.parent == testWnd) - break; - else - testWnd = testWnd.parent; - } - - match = location; - locationText = match.text.replace(/^.*?#/, '#'); - location = locationText; - - if (!match.isActiveOnDomain(docDomain)) - return true; - - let exception = ElemHide.getException(match, docDomain); - if (exception) - { - FilterStorage.increaseHitCount(exception, wnd); - RequestNotifier.addNodeData(node, topWnd, contentType, docDomain, thirdParty, locationText, exception); - return true; - } - } - - let thirdParty = (contentType == Policy.type.ELEMHIDE ? false : isThirdParty(location, docDomain)); - - if (!match && Prefs.enabled) - { - match = defaultMatcher.matchesAny(locationText, Policy.typeDescr[contentType] || "", docDomain, thirdParty, privatenode); - if (match instanceof BlockingFilter && node.ownerDocument && !(contentType in Policy.nonVisual)) - { - let prefCollapse = (match.collapse != null ? match.collapse : !Prefs.fastcollapse); - if (collapse || prefCollapse) - schedulePostProcess(node); - } - - // Track mouse events for objects - if (!match && contentType == Policy.type.OBJECT && node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) - { - node.addEventListener("mouseover", objectMouseEventHander, true); - node.addEventListener("mouseout", objectMouseEventHander, true); - } - } - - // Store node data - RequestNotifier.addNodeData(node, topWnd, contentType, docDomain, thirdParty, locationText, match); - if (match) - FilterStorage.increaseHitCount(match, wnd); - - return !match || match instanceof WhitelistFilter; - }, - - /** - * Checks whether the location's scheme is blockable. - * @param location {nsIURI} - * @return {Boolean} - */ - isBlockableScheme: function(location) - { - return !(location.scheme in Policy.whitelistSchemes); - }, - - /** - * Checks whether a page is whitelisted. - * @param {String} url - * @param {String} [parentUrl] location of the parent page - * @return {Filter} filter that matched the URL or null if not whitelisted - */ - isWhitelisted: function(url, parentUrl) - { - if (!url) - return null; - - // Do not apply exception rules to schemes on our whitelistschemes list. - let match = /^([\w\-]+):/.exec(url); - if (match && match[1] in Policy.whitelistSchemes) - return null; - - if (!parentUrl) - parentUrl = url; - - // Ignore fragment identifier - let index = url.indexOf("#"); - if (index >= 0) - url = url.substring(0, index); - - let result = defaultMatcher.matchesAny(url, "DOCUMENT", getHostname(parentUrl), false); - return (result instanceof WhitelistFilter ? result : null); - }, - - /** - * Checks whether the page loaded in a window is whitelisted. - * @param wnd {nsIDOMWindow} - * @return {Filter} matching exception rule or null if not whitelisted - */ - isWindowWhitelisted: function(wnd) - { - return Policy.isWhitelisted(getWindowLocation(wnd)); - }, - - - /** - * Asynchronously re-checks filters for given nodes. - */ - refilterNodes: function(/**Node[]*/ nodes, /**RequestEntry*/ entry) - { - // Ignore nodes that have been blocked already - if (entry.filter && !(entry.filter instanceof WhitelistFilter)) - return; - - for (let node of nodes) - Utils.runAsync(refilterNode, this, node, entry); - } -}; -Policy.init(); - -/** - * Actual nsIContentPolicy and nsIChannelEventSink implementation - * @class - */ -let PolicyImplementation = -{ - classDescription: "Adblock Plus content policy", - classID: Components.ID("cfeaabe6-1dd1-11b2-a0c6-cb5c268894c9"), - contractID: "@adblockplus.org/abp/policy;1", - xpcom_categories: ["content-policy", "net-channel-event-sinks"], - - /** - * Registers the content policy on startup. - */ - init: function() - { - let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); - try - { - registrar.registerFactory(this.classID, this.classDescription, this.contractID, this); - } - catch (e if e.result == Cr.NS_ERROR_FACTORY_EXISTS) - { - // See bug 924340 - it might be too early to init now, the old version - // we are replacing didn't finish removing itself yet. - Utils.runAsync(this.init.bind(this)); - return; - } - - let catMan = Utils.categoryManager; - for (let category of this.xpcom_categories) - catMan.addCategoryEntry(category, this.contractID, this.contractID, false, true); - - // http-on-opening-request is new in Gecko 18, http-on-modify-request can - // be used in earlier releases. - let httpTopic = "http-on-opening-request"; - if (Services.vc.compare(Utils.platformVersion, "18.0") < 0) - httpTopic = "http-on-modify-request"; - - Services.obs.addObserver(this, httpTopic, true); - Services.obs.addObserver(this, "content-document-global-created", true); - Services.obs.addObserver(this, "xpcom-category-entry-removed", true); - Services.obs.addObserver(this, "xpcom-category-cleared", true); - - onShutdown.add(function() - { - // Our category observers should be removed before changing category - // memberships, just in case. - Services.obs.removeObserver(this, httpTopic); - Services.obs.removeObserver(this, "content-document-global-created"); - Services.obs.removeObserver(this, "xpcom-category-entry-removed"); - Services.obs.removeObserver(this, "xpcom-category-cleared"); - - for (let category of this.xpcom_categories) - catMan.deleteCategoryEntry(category, this.contractID, false); - - // This needs to run asynchronously, see bug 753687 - Utils.runAsync(function() - { - registrar.unregisterFactory(this.classID, this); - }.bind(this)); - - this.previousRequest = null; - }.bind(this)); - }, - - // - // nsISupports interface implementation - // - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver, - Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]), - - // - // nsIContentPolicy interface implementation - // - - shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTypeGuess, extra) - { - // Ignore requests without context and top-level documents - if (!node || contentType == Policy.type.DOCUMENT) - return Ci.nsIContentPolicy.ACCEPT; - - // Ignore standalone objects - if (contentType == Policy.type.OBJECT && node.ownerDocument && !/^text\/|[+\/]xml$/.test(node.ownerDocument.contentType)) - return Ci.nsIContentPolicy.ACCEPT; - - let wnd = Utils.getWindow(node); - if (!wnd) - return Ci.nsIContentPolicy.ACCEPT; - - // Ignore whitelisted schemes - let location = Utils.unwrapURL(contentLocation); - if (!Policy.isBlockableScheme(location)) - return Ci.nsIContentPolicy.ACCEPT; - - // Interpret unknown types as "other" - if (!(contentType in Policy.typeDescr)) - contentType = Policy.type.OTHER; - - let result = Policy.processNode(wnd, node, contentType, location, false); - if (result) - { - // We didn't block this request so we will probably see it again in - // http-on-opening-request. Keep it so that we can associate it with the - // channel there - will be needed in case of redirect. - this.previousRequest = [location, contentType]; - } - return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQUEST); - }, - - shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) - { - return Ci.nsIContentPolicy.ACCEPT; - }, - - // - // nsIObserver interface implementation - // - observe: function(subject, topic, data, additional) - { - switch (topic) - { - case "content-document-global-created": - { - if (!(subject instanceof Ci.nsIDOMWindow) || !subject.opener) - return; - - let uri = additional || Utils.makeURI(subject.location.href); - if (!Policy.processNode(subject.opener, subject.opener.document, Policy.type.POPUP, uri, false)) - { - subject.stop(); - Utils.runAsync(subject.close, subject); - } - else if (uri.spec == "about:blank") - { - // An about:blank pop-up most likely means that a load will be - // initiated synchronously. Set a flag for our "http-on-opening-request" - // handler. - this.expectingPopupLoad = true; - Utils.runAsync(function() - { - this.expectingPopupLoad = false; - }); - } - break; - } - case "http-on-opening-request": - case "http-on-modify-request": - { - if (!(subject instanceof Ci.nsIHttpChannel)) - return; - - if (this.previousRequest && subject.URI == this.previousRequest[0] && - subject instanceof Ci.nsIWritablePropertyBag) - { - // We just handled a content policy call for this request - associate - // the data with the channel so that we can find it in case of a redirect. - subject.setProperty("abpRequestType", this.previousRequest[1]); - this.previousRequest = null; - } - - if (this.expectingPopupLoad) - { - let wnd = Utils.getRequestWindow(subject); - if (wnd && wnd.opener && wnd.location.href == "about:blank") - { - this.observe(wnd, "content-document-global-created", null, subject.URI); - if (subject instanceof Ci.nsIWritablePropertyBag) - subject.setProperty("abpRequestType", Policy.type.POPUP); - } - } - - break; - } - case "xpcom-category-entry-removed": - case "xpcom-category-cleared": - { - let category = data; - if (this.xpcom_categories.indexOf(category) < 0) - return; - - if (topic == "xpcom-category-entry-removed" && - subject instanceof Ci.nsISupportsCString && - subject.data != this.contractID) - { - return; - } - - // Our category entry was removed, make sure to add it back - let catMan = Utils.categoryManager; - catMan.addCategoryEntry(category, this.contractID, this.contractID, false, true); - break; - } - } - }, - - // - // nsIChannelEventSink interface implementation - // - - asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) - { - let result = Cr.NS_OK; - try - { - // Try to retrieve previously stored request data from the channel - let contentType; - if (oldChannel instanceof Ci.nsIWritablePropertyBag) - { - try - { - contentType = oldChannel.getProperty("abpRequestType"); - } - catch(e) - { - // No data attached, ignore this redirect - return; - } - } - - let newLocation = null; - try - { - newLocation = newChannel.URI; - } catch(e2) {} - if (!newLocation) - return; - - let wnd = Utils.getRequestWindow(newChannel); - if (!wnd) - return; - - if (contentType == Policy.type.SUBDOCUMENT && wnd.parent == wnd.top && wnd.opener) - { - // This is a window opened in a new tab miscategorized as frame load, - // see bug 467514. Get the frame as context to be at least consistent. - wnd = wnd.opener; - } - - if (contentType == Policy.type.POPUP && wnd.opener) - { - // Popups are initiated by their opener, not their own window. - wnd = wnd.opener; - } - - if (!Policy.processNode(wnd, wnd.document, contentType, newLocation, false)) - result = Cr.NS_BINDING_ABORTED; - } - catch (e) - { - // We shouldn't throw exceptions here - this will prevent the redirect. - Cu.reportError(e); - } - finally - { - callback.onRedirectVerifyCallback(result); - } - }, - - // - // nsIFactory interface implementation - // - - createInstance: function(outer, iid) - { - if (outer) - throw Cr.NS_ERROR_NO_AGGREGATION; - return this.QueryInterface(iid); - } -}; -PolicyImplementation.init(); - -/** - * Nodes scheduled for post-processing (might be null). - * @type Array of Node - */ -let scheduledNodes = null; - -/** - * Schedules a node for post-processing. - */ -function schedulePostProcess(/**Element*/ node) -{ - if (scheduledNodes) - scheduledNodes.push(node); - else - { - scheduledNodes = [node]; - Utils.runAsync(postProcessNodes); - } -} - -/** - * Processes nodes scheduled for post-processing (typically hides them). - */ -function postProcessNodes() -{ - let nodes = scheduledNodes; - scheduledNodes = null; - - for (let node of nodes) - { - // adjust frameset's cols/rows for frames - let parentNode = node.parentNode; - if (parentNode && parentNode instanceof Ci.nsIDOMHTMLFrameSetElement) - { - let hasCols = (parentNode.cols && parentNode.cols.indexOf(",") > 0); - let hasRows = (parentNode.rows && parentNode.rows.indexOf(",") > 0); - if ((hasCols || hasRows) && !(hasCols && hasRows)) - { - let index = -1; - for (let frame = node; frame; frame = frame.previousSibling) - if (frame instanceof Ci.nsIDOMHTMLFrameElement || frame instanceof Ci.nsIDOMHTMLFrameSetElement) - index++; - - let property = (hasCols ? "cols" : "rows"); - let weights = parentNode[property].split(","); - weights[index] = "0"; - parentNode[property] = weights.join(","); - } - } - else - node.classList.add(collapsedClass); - } -} - -/** - * Extracts the hostname from a URL (might return null). - */ -function getHostname(/**String*/ url) /**String*/ -{ - try - { - return Utils.unwrapURL(url).host; - } - catch(e) - { - return null; - } -} - -/** - * Retrieves the location of a window. - * @param wnd {nsIDOMWindow} - * @return {String} window location or null on failure - */ -function getWindowLocation(wnd) -{ - if ("name" in wnd && wnd.name == "messagepane") - { - // Thunderbird branch - try - { - let mailWnd = wnd.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - - // Typically we get a wrapped mail window here, need to unwrap - try - { - mailWnd = mailWnd.wrappedJSObject; - } catch(e) {} - - if ("currentHeaderData" in mailWnd && "content-base" in mailWnd.currentHeaderData) - { - return mailWnd.currentHeaderData["content-base"].headerValue; - } - else if ("currentHeaderData" in mailWnd && "from" in mailWnd.currentHeaderData) - { - let emailAddress = Utils.headerParser.extractHeaderAddressMailboxes(mailWnd.currentHeaderData.from.headerValue); - if (emailAddress) - return 'mailto:' + emailAddress.replace(/^[\s"]+/, "").replace(/[\s"]+$/, "").replace(/\s/g, '%20'); - } - } catch(e) {} - } - - // Firefox branch - return wnd.location.href; -} - -/** - * Checks whether the location's origin is different from document's origin. - */ -function isThirdParty(/**nsIURI*/location, /**String*/ docDomain) /**Boolean*/ -{ - if (!location || !docDomain) - return true; - - try - { - return Utils.effectiveTLD.getBaseDomain(location) != Utils.effectiveTLD.getBaseDomainFromHost(docDomain); - } - catch (e) - { - // EffectiveTLDService throws on IP addresses, just compare the host name - let host = ""; - try - { - host = location.host; - } catch (e) {} - return host != docDomain; - } -} - -/** - * Re-checks filters on an element. - */ -function refilterNode(/**Node*/ node, /**RequestEntry*/ entry) -{ - let wnd = Utils.getWindow(node); - if (!wnd || wnd.closed) - return; - - if (entry.type == Policy.type.OBJECT) - { - node.removeEventListener("mouseover", objectMouseEventHander, true); - node.removeEventListener("mouseout", objectMouseEventHander, true); - } - Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true); -} |