<commit_before>/* See license.txt for terms of usage */

define([
    "firebug/lib/xpcom",
    "firebug/lib/object",
    "firebug/lib/locale",
    "firebug/lib/domplate",
    "firebug/lib/dom",
    "firebug/lib/options",
    "firebug/lib/persist",
    "firebug/lib/string",
    "firebug/lib/http",
    "firebug/lib/css",
    "firebug/lib/events",
    "firebug/lib/array",
    "firebug/cookies/baseObserver",
    "firebug/cookies/menuUtils",
    "firebug/cookies/cookieReps",
    "firebug/cookies/cookieUtils",
    "firebug/cookies/cookie",
    "firebug/cookies/breakpoints",
    "firebug/cookies/cookieObserver",
    "firebug/cookies/cookieClipboard",
    "firebug/chrome/tabWatcher",
    "firebug/cookies/httpObserver",
    "firebug/lib/system",
    "firebug/cookies/cookie",
    "firebug/cookies/cookiePermissions",
    "firebug/cookies/editCookie",
    "firebug/trace/traceListener",
    "firebug/trace/traceModule",
    "firebug/chrome/firefox",
    "firebug/cookies/legacy",
],
function(Xpcom, Obj, Locale, Domplate, Dom, Options, Persist, Str, Http, Css, Events, Arr,
    BaseObserver, MenuUtils, CookieReps, CookieUtils, Cookier, Breakpoints, CookieObserver,
    CookieClipboard, TabWatcher, HttpObserver, System, Cookie, CookiePermissions, EditCookie,
    TraceListener, TraceModule, Firefox) {

with (Domplate) {

// ********************************************************************************************* //
// Constants

const Cc = Components.classes;
const Ci = Components.interfaces;

// Firefox Preferences
const networkPrefDomain = "network.cookie";
const cookieBehaviorPref = "cookieBehavior";
const cookieLifeTimePref = "lifetimePolicy";

// Cookies preferences
const clearWhenDeny = "cookies.clearWhenDeny";
const defaultExpireTime = "cookies.defaultExpireTime";
const removeConfirmation = "cookies.removeConfirmation";
const removeSessionConfirmation = "cookies.removeSessionConfirmation";

// Services
const cookieManager = Xpcom.CCSV("@mozilla.org/cookiemanager;1", "nsICookieManager2");
const observerService = Xpcom.CCSV("@mozilla.org/observer-service;1", "nsIObserverService");
const prompts = Xpcom.CCSV("@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService");

// Preferences
const PrefService = Cc["@mozilla.org/preferences-service;1"];
const prefService = PrefService.getService(Ci.nsIPrefService);
const prefs = PrefService.getService(Ci.nsIPrefBranch2);

// Cookie panel ID.
const panelName = "cookies";

// Helper array for prematurely created contexts
var contexts = new Array();

// Register stylesheet in Firebug. This method is introduced in Firebug 1.6
Firebug.registerStylesheet("chrome://firebug/skin/cookies/cookies.css");

// ********************************************************************************************* //
// Module Implementation

/**
 * @module This class represents a <i>module</i> for Cookies panel.
 * The module supports activation (enable/disable of the Cookies panel).
 * This functionality has been introduced in Firebug 1.2 and makes possible
 * to control activity of Firebug panels in order to avoid (performance) expensive
 * features.
 */
Firebug.CookieModule = Obj.extend(Firebug.ActivableModule,
/** @lends Firebug.CookieModule */
{
    contexts: contexts,

    // Set to true if all hooks for monitoring cookies are registered; otherwise false.
    observersRegistered: false,

    /**
     * Called by Firebug when Firefox window is opened.
     *
     * @param {String} prefDomain Preference domain (e.g. extensions.firebug)
     * @param {Array} prefNames Default Firebug preference array.
     */
    initialize: function(prefDomain, prefNames)
    {
        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.CookieModule.initialize; ");

        this.traceListener = new TraceListener("cookies.", "DBG_COOKIES", true,
            "chrome://firebug/skin/cookies/trace.css");

        TraceModule.addListener(this.traceListener);

        this.panelName = panelName;
        this.description = Locale.$STR("cookies.modulemanager.description");

        Firebug.ActivableModule.initialize.apply(this, arguments);

        var permTooltip = Firebug.chrome.$("fcPermTooltip");
        permTooltip.fcEnabled = true;

        // All the necessary observers are registered by default. Even if the 
        // panel can be disabled (entirely or for a specific host) there is
        // no simple way to find out this now, as the context isn't available. 
        // All will be unregistered again in the initContext (if necessary).
        // There is no big overhead, the initContext is called just after the
        // first document request.
        //this.registerObservers();

        // Register listener for NetInfoBody (if the API is available) so,
        // a new tab (Cookies) can be appended into the Net panel request info.
        var netInfoBody = Firebug.NetMonitor.NetInfoBody;
        if ("addListener" in netInfoBody)
            netInfoBody.addListener(this.NetInfoBody);

        // Register listener within the Console panel. If document.cookie property
        // is logged, formatted output is used.
        //Firebug.Console.addListener(this.ConsoleListener);

        // Register debugger listener for providing cookie-breakpoints.
        //Firebug.Debugger.addListener(this.DebuggerListener);

        // Dynamically overlay Break on Next button in FB 1.5.1
        // There is a small decoration coming from each panel.
        var bonStack = Firebug.chrome.$("fbBreakOnNextButtonStack");
        if (bonStack)
        {
            var image = document.createElement("image");
            image.setAttribute("id", "fbBreakOnImageCookies");
            image.setAttribute("class", "fbBreakOnImage");
            image.setAttribute("src", "chrome://firebug/skin/cookies/breakOnCookieSingle.png");
            bonStack.appendChild(image);
        }

        Firebug.registerUIListener(this);
    },

    initializeUI: function()
    {
        Firebug.ActivableModule.initializeUI.apply(this, arguments);

        // Append the styleesheet to a new console popup panel introduced in Firebug 1.6
        this.addStyleSheet(null);

        Dom.collapse(Firebug.chrome.$("fbConsoleFilter-cookies"), false);
    },

    /**
     * Peforms clean up when Firebug is destroyed.
     * Called by the framework when Firebug is closed for an existing Firefox window.
     */
    shutdown: function()
    {
        this.unregisterObservers();

        // Support for trace-console customization in Firebug 1.3
        TraceModule.removeListener(this.traceListener);

        var netInfoBody = Firebug.NetMonitor.NetInfoBody;
        if ("removeListener" in netInfoBody)
            netInfoBody.removeListener(this.NetInfoBody);

        //Firebug.Console.removeListener(this.ConsoleListener);
        //Firebug.Debugger.removeListener(this.DebuggerListener);

        Firebug.unregisterUIListener(this);
    },

    registerObservers: function()
    {
        if (this.observersRegistered)
        {
            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.cookieModule.registerObservers; Observers ALREADY registered");
            return;
        }

        observerService.addObserver(HttpObserver, "http-on-modify-request", false);
        observerService.addObserver(HttpObserver, "http-on-examine-response", false);
        observerService.addObserver(PermissionObserver, "perm-changed", false);
        registerCookieObserver(CookieObserver);
        prefs.addObserver(networkPrefDomain, PrefObserver, false);

        this.observersRegistered = true;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.cookieModule.registerObservers;");
    },

    unregisterObservers: function(context)
    {
        if (!this.observersRegistered)
        {
            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.cookieModule.registerObservers; " +
                    "Observers ALREADY un-registered");
            return;
        }

        observerService.removeObserver(HttpObserver, "http-on-modify-request");
        observerService.removeObserver(HttpObserver, "http-on-examine-response");
        observerService.removeObserver(PermissionObserver, "perm-changed");
        unregisterCookieObserver(CookieObserver);
        prefs.removeObserver(networkPrefDomain, PrefObserver);

        this.observersRegistered = false;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.cookieModule.unregisterObservers;");
    },

    // Helper context
    initTempContext: function(tempContext)
    {
        tempContext.cookieTempObserver = registerCookieObserver(new CookieTempObserver(tempContext));

        // Create sub-context for cookies.
        tempContext.cookies = {};
        tempContext.cookies.activeHosts = [];
    },

    destroyTempContext: function(tempContext, context)
    {
        if (!tempContext)
            return;

        if (FBTrace.DBG_COOKIES)
        {
            FBTrace.sysout("cookies.Copy " + tempContext.events.length +
                " events to real-context.");

            var message = "cookies.Copy active hosts (";
            for (var host in tempContext.cookies.activeHosts)
                message += host + ", ";
            message = message.substring(0, message.length - 2);
            message += ") from temp context into the real context.";
            FBTrace.sysout(message, tempContext);
        }

        // Copy all active hosts on the page. In case of redirects or embedded IFrames, there
        // can be more hosts (domains) involved on the page. Cookies must be displayed for
        // all of them.
        context.cookies.activeHosts = cloneMap(tempContext.cookies.activeHosts);

        // Clone all active (received) cookies on the page.
        // This is probably not necessary, as the first cookie is received
        // in http-on-examine-response and at that time the real context
        // is already created.
        context.cookies.activeCookies = cloneMap(tempContext.cookies.activeCookies);

        // Fire all lost cookie events (those from the temp context).
        var events = tempContext.events;
        for (var i=0; i<events.length; i++) {
            var e = events[i];
            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.Fire fake cookie event: " + e.topic + ", " + e.data + "\n");
            CookieObserver.observe(e.subject, e.topic, e.data);
        }

        delete tempContext.cookies.activeHosts;
        delete tempContext.cookies.activeCookies;
        delete tempContext.cookies;

        // Unregister temporary cookie observer.
        tempContext.cookieTempObserver = unregisterCookieObserver(tempContext.cookieTempObserver);
    },

    /**
     * Called by the framework when a context is created for Firefox tab.
     * 
     *  @param {Firebug.TabContext} Context for the current Firefox tab.
     */
    initContext: function(context)
    {
        var tabId = Firebug.getTabIdForWindow(context.window);

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.INIT real context for: " + tabId + ", " +
                context.getName());

        // Create sub-context for cookies. 
        // xxxHonza: the cookies object exists within the context even if 
        // the panel is disabled.
        context.cookies = {};
        context.cookies.activeHosts = [];

        // Initialize custom path filter for this context
        context.cookies.pathFilter = "/";

        // List of breakpoints.
        context.cookies.breakpoints = new CookieBreakpointGroup();
        context.cookies.breakpoints.load(context);

        // The temp context isn't created e.g. for empty tabs, chrome pages.
        var tempContext = contexts[tabId];
        if (tempContext)
        {
            this.destroyTempContext(tempContext, context);
            delete contexts[tabId];

            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.DESTROY temporary context, tabId: " + tempContext.tabId);
        }

        // The base class must be called after the context for Cookies panel is 
        // properly initialized. The panel can be created inside this function
        // (within Firebug.ActivableModule.enablePanel), which can result in
        // calling CookiePanel.initialize method. This method directly calls
        // CookiePanel.refresh, which needs the context.cookies object ready.
        Firebug.ActivableModule.initContext.apply(this, arguments);

        // Unregister all observers if the panel is disabled.
        if (!this.isEnabled(context))
            this.unregisterObservers(context);
    },

    destroyContext: function(context) 
    {
        Firebug.ActivableModule.destroyContext.apply(this, arguments);

        if (!context.cookies)
        {
            if (FBTrace.DBG_COOKIES)
            {
                var tabId = Firebug.getTabIdForWindow(context.window);
                FBTrace.sysout("cookies.DESTROY context ERROR: No context.cookies available, tabId: " +
                    tabId + ", " + context.getName());
            }
            return;
        }

        context.cookies.breakpoints.store(context);

        for (var p in context.cookies)
            delete context.cookies[p];

        delete context.cookies;

        if (FBTrace.DBG_COOKIES)
        {
            var tabId = Firebug.getTabIdForWindow(context.window);
            FBTrace.sysout("cookies.DESTROY context, tabId: " + tabId +
                ", " + context.getName());
        }
    },

    addStyleSheet: function(panel)
    {
        // Use registration function instead (introduced in Firebug 1.6)
        if (Firebug.registerStylesheet)
            return;

        function privateAppend(doc)
        {
            // Make sure the stylesheet isn't appended twice.
            if (!Firebug.chrome.$("fcStyles", doc))
            {
                var styleSheet = createStyleSheet(doc, "chrome://firebug/skin/cookies/cookies.css");
                styleSheet.setAttribute("id", "fcStyles");
                addStyleSheet(doc, styleSheet);
            }
        }

        if (panel)
            privateAppend(panel.document)

        // Firebug 1.6 introduces another panel for console preview on other panels
        // The allows to use command line in other panels too.
        var preview = Firebug.chrome.$("fbCommandPreviewBrowser");
        if (preview)
            privateAppend(preview.contentDocument);
    },

    updateOption: function(name, value)
    {
        if (name == "consoleFilterTypes")
        {
            this.updateConsoleFilter();
        }
    },

    updateConsoleFilter: function()
    {
        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.updateConsoleFilter;");

        if (!Firebug.currentContext)
            return;

        // The panel can be disabled.
        var panel = Firebug.currentContext.getPanel("console");
        if (!panel)
            return;

        var panelNode = panel.panelNode;
        var className = "hideType-cookies";
        var filterTypes = Firebug.consoleFilterTypes;

        Css.setClass(panelNode, className);

        var positiveFilters = ["all", "cookies"];
        for (var i=0; i<positiveFilters.length; i++)
        {
            if (filterTypes.indexOf(positiveFilters[i]) >= 0)
            {
                Css.removeClass(panelNode, className);
                break;
            }
        }
    },

    showPanel: function(browser, panel)
    {
        // Update panel's toolbar
        var isCookiePanel = panel && panel.name == panelName;

        // Firebug 1.4, chrome changes.
        var chrome = browser.chrome ? browser.chrome : Firebug.chrome;

        var cookieButtons = Firebug.chrome.$("fbCookieButtons");
        Dom.collapse(cookieButtons, !isCookiePanel);

        // The console panel can be displayed sooner than the Cookies
        // panel, in such a case the Stylesheet must be ready as
        // there are cookies logs in the console.
        // Cookie table is also used within the net panel.
        if (panel && (panel.name == "console" || panel.name == "net"))
            this.addStyleSheet(panel);
    },

    watchWindow: function(context, win) 
    {
        context.window.addEventListener("beforeunload", this.onBeforeUnload, false);
    },

    onBeforeUnload: function(event) 
    {
        var view = event.target.defaultView;
        var context = TabWatcher.getContextByWindow(view);
        if (!context)
            return;

        var panel = context.getPanel(panelName, true);
        if (panel)
            panel.clear();

        if (FBTrace.DBG_COOKIES || FBTrace.DBG_ERRORS)
        {
            var tabId = Firebug.getTabIdForWindow(view);

            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.On before unload tab:  " + tabId);

            if (contexts[tabId])
            {
                delete contexts[tabId];

                if (FBTrace.DBG_COOKIES)
                    FBTrace.sysout("cookies.CookieModule.onBeforeUnload; There is a temp context leak!");
            }
        }
    },

    /**
     * Creates a new cookie in the browser.
     * This method is used by {@link EditCookie} dialog and also when a cookie is
     * pasted from the clipboard.
     *
     * @param {Cookie} Cookie object with appropriate properties. See {@link Cookie} object.
     */
    createCookie: function(cookie)
    {
        try
        {
            var uri = cookie.getURI();
            if (!uri)
                return;

            var c = cookie.cookie;

            // Fix for issue 34. The domain must be included in the cookieString if it 
            // starts with "." But don't include it otherwise, since the "." would be 
            // appended by the service.
            var host = cookie.cookie.host;
            var cookieString = cookie.toString(!(host.charAt(0) == "."));

            // Fix for issue 37: httpOnly cookies, and issue 47: Cannot change the HttpOnly flag
            // HttpOnly cookies can't be changed by setCookie string
            // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=178993
            //cookieService.setCookieString(uri, null, cookieString, null);

            // Doesn't work in FF4 (issue 95)
            //cookieService.setCookieStringFromHttp(uri, uri, null, cookieString,
            //    c.expires, null);

            //xxxHonza: in what cases the cookie should be removed?
            //var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
            //cm.remove(c.host, c.name, c.path, false);

            var isSession = c.expires ? false : true;
            var cm2 = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
            cm2.add(c.host, c.path, c.name, c.rawValue, c.isSecure, c.isHttpOnly, isSession,
                c.expires || Math.round((new Date()).getTime() / 1000 + 9999999999));

            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.createCookie: set cookie string: " + cookieString, cookie);

            // xxxHonza: this shouldn't be necessary, but sometimes the CookieObserver
            // is not triggered.
            TabWatcher.iterateContexts(function(context)
            {
                context.getPanel(panelName).refresh();
            });
        }
        catch (e)
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("cookies.createCookie: set cookie string ERROR " +
                    cookieString, e);
        }
    },

    removeCookie: function(host, name, path)
    {
        cookieManager.remove(host, name, path, false);

        // xxxHonza: this shouldn't be necessary, but sometimes the CookieObserver
        // is not triggered.
        TabWatcher.iterateContexts(function(context)
        {
            context.getPanel(panelName).refresh();
        });
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // Support for ActivableModule 1.6

    /**
     * It's just here to exists (calling base class only)
     */
    isEnabled: function(context)
    {
        return Firebug.ActivableModule.isEnabled.apply(this, arguments);
    },

    /**
     * Called when an observer (e.g. panel) is added/removed into/from the model.
     * This is the moment when the model needs to decide whether to activate.
     */
    onObserverChange: function(observer)
    {
        if (this.hasObservers())
            TabWatcher.iterateContexts(Firebug.CookieModule.registerObservers);
        else
            TabWatcher.iterateContexts(Firebug.CookieModule.unregisterObservers);

        this.setStatus();
    },

    onSuspendFirebug: function()
    {
        TabWatcher.iterateContexts(Firebug.CookieModule.unregisterObservers);

        this.setStatus();

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onSuspendFirebug");
    },

    onResumeFirebug: function(context)
    {
        if (Firebug.CookieModule.isAlwaysEnabled())
            TabWatcher.iterateContexts(Firebug.CookieModule.registerObservers);

        this.setStatus();

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onResumeFirebug");
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

    setStatus: function()
    {
        var fbStatus = Firefox.getElementById("firebugStatus");
        if (fbStatus)
        {
            if (this.hasObservers())
                fbStatus.setAttribute(panelName, "on");
            else
                fbStatus.removeAttribute(panelName);
        }
        else
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("cookies.setStatus ERROR no firebugStatus element");
        }
    },

    getMenuLabel: function(option, location)
    {
        var host = getURIHost(location);

        // In case of local files or system pages use this labels instead of host.
        // xxxHonza: the panel should be automatically disabled for local files
        // and system pages as there are no cookies associated.
        // These options shouldn't be available at all.
        if (isSystemURL(location.spec))
            host = Locale.$STR("cookies.SystemPages");
        else if (!getURIHost(location))
            host = Locale.$STR("cookies.LocalFiles");

        // Translate these two options in panel activable menu from cookies.properties
        switch (option)
        {
        case "disable-site":
            return Locale.$STRF("cookies.HostDisable", [host]);
        case "enable-site":
            return Locale.$STRF("cookies.HostEnable", [host]);
        }

        return Firebug.ActivableModule.getMenuLabel.apply(this, arguments);
    },

    // xxxHonza: This method is overriden just to provide translated strings from
    // cookies.properties file.
    openPermissions: function(event, context)
    {
        Events.cancelEvent(event);

        var browserURI = Firebug.chrome.getBrowserURI(context);
        var host = this.getHostForURI(browserURI);

        var params = {
            permissionType: this.getPrefDomain(),
            windowTitle: Locale.$STR(this.panelName + ".Permissions"),
            introText: Locale.$STR(this.panelName + ".PermissionsIntro"),
            blockVisible: true,
            sessionVisible: false,
            allowVisible: true,
            prefilledHost: host,
        };

        openWindow("Browser:Permissions", "chrome://browser/content/preferences/permissions.xul",
            "", params);
    },

    // UI Commands
    onRemoveAllShowTooltip: function(tooltip, context)
    {
        tooltip.label = Locale.$STR("cookies.removeall.tooltip");
        return true;
    },

    onRemoveAllSessionShowTooltip: function(tooltip, context)
    {
        tooltip.label = Locale.$STR("cookies.removeallsession.tooltip");
        return true;
    },

    removeCookies: function(context, filter)
    {
        var panel = context.getPanel(panelName, true);
        if (!panel)
            return;

        for (var host in context.cookies.activeHosts)
        {
            var cookieEnumerator = cookieManager.getCookiesFromHost(host);

            while(cookieEnumerator.hasMoreElements())
            {
                var cookie = cookieEnumerator.getNext();
                cookie = cookie.QueryInterface(Ci.nsICookie2);
                var sessionCookieToRemove = typeof filter.session != "undefined" && filter.session &&
                    cookie.isSession;
            }
        }

        /*for (var i=0; i<cookies.length; i++)
            CookieReps.CookieRow.onRemove(cookies[i]);*/
    },

    onRemoveAll: function(context)
    {
        if (Options.get(removeConfirmation))
        {
            var check = {value: false};
            var flags = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_YES +  
                prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_NO;  

            if (!prompts.confirmEx(context.chrome.window, Locale.$STR("Firebug"),
                Locale.$STR("cookies.confirm.removeall"), flags, "", "", "",
                Locale.$STR("cookies.msg.Do_not_show_this_message_again"), check) == 0)
            {
                return;
            }

            // Update 'Remove Cookies' confirmation option according to the value
            // of the dialog's "do not show again" checkbox.
            Options.set(removeConfirmation, !check.value);
        }

        Firebug.CookieModule.removeCookies(context, false);
    },

    onRemoveAllSession: function(context)
    {
        if (Options.get(removeSessionConfirmation))
        {
            var check = {value: false};
            var flags = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_YES +  
                prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_NO;  

            if (!prompts.confirmEx(context.chrome.window, Locale.$STR("Firebug"),
                Locale.$STR("cookies.confirm.removeallsession"), flags, "", "", "",
                Locale.$STR("cookies.msg.Do_not_show_this_message_again"), check) == 0)
            {
                return;
            }

            // Update 'Remove Session Cookies' confirmation option according to the value
            // of the dialog's "do not show again" checkbox.
            Options.set(removeSessionConfirmation, !check.value)
        }

        Firebug.CookieModule.removeCookies(context, true);
    },

    onCreateCookieShowTooltip: function(tooltip, context)
    {
        var host = context.window.location.host;
        tooltip.label = Locale.$STRF("cookies.createcookie.tooltip", [host]);
        return true;
    },

    onCreateCookie: function(context)
    {
        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onCreateCookie");

        // There is an excepion if the window is closed or not initialized (empty tab)
        var host;
        try {
            host = context.window.location.host
        }
        catch (err) {
            alert(Locale.$STR("cookies.message.There_is_no_active_page"));
            return;<commit_msg>Fixed and enhanced cookie removal (was broken in d98ba549ed)<commit_after>/* See license.txt for terms of usage */

define([
    "firebug/lib/xpcom",
    "firebug/lib/object",
    "firebug/lib/locale",
    "firebug/lib/domplate",
    "firebug/lib/dom",
    "firebug/lib/options",
    "firebug/lib/persist",
    "firebug/lib/string",
    "firebug/lib/http",
    "firebug/lib/css",
    "firebug/lib/events",
    "firebug/lib/array",
    "firebug/cookies/baseObserver",
    "firebug/cookies/menuUtils",
    "firebug/cookies/cookieReps",
    "firebug/cookies/cookieUtils",
    "firebug/cookies/cookie",
    "firebug/cookies/breakpoints",
    "firebug/cookies/cookieObserver",
    "firebug/cookies/cookieClipboard",
    "firebug/chrome/tabWatcher",
    "firebug/cookies/httpObserver",
    "firebug/lib/system",
    "firebug/cookies/cookie",
    "firebug/cookies/cookiePermissions",
    "firebug/cookies/editCookie",
    "firebug/trace/traceListener",
    "firebug/trace/traceModule",
    "firebug/chrome/firefox",
    "firebug/cookies/legacy",
],
function(Xpcom, Obj, Locale, Domplate, Dom, Options, Persist, Str, Http, Css, Events, Arr,
    BaseObserver, MenuUtils, CookieReps, CookieUtils, Cookier, Breakpoints, CookieObserver,
    CookieClipboard, TabWatcher, HttpObserver, System, Cookie, CookiePermissions, EditCookie,
    TraceListener, TraceModule, Firefox) {

with (Domplate) {

// ********************************************************************************************* //
// Constants

const Cc = Components.classes;
const Ci = Components.interfaces;

// Firefox Preferences
const networkPrefDomain = "network.cookie";
const cookieBehaviorPref = "cookieBehavior";
const cookieLifeTimePref = "lifetimePolicy";

// Cookies preferences
const clearWhenDeny = "cookies.clearWhenDeny";
const defaultExpireTime = "cookies.defaultExpireTime";
const removeConfirmation = "cookies.removeConfirmation";
const removeSessionConfirmation = "cookies.removeSessionConfirmation";

// Services
const cookieManager = Xpcom.CCSV("@mozilla.org/cookiemanager;1", "nsICookieManager2");
const observerService = Xpcom.CCSV("@mozilla.org/observer-service;1", "nsIObserverService");
const prompts = Xpcom.CCSV("@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService");

// Preferences
const PrefService = Cc["@mozilla.org/preferences-service;1"];
const prefService = PrefService.getService(Ci.nsIPrefService);
const prefs = PrefService.getService(Ci.nsIPrefBranch2);

// Cookie panel ID.
const panelName = "cookies";

// Helper array for prematurely created contexts
var contexts = new Array();

// Register stylesheet in Firebug. This method is introduced in Firebug 1.6
Firebug.registerStylesheet("chrome://firebug/skin/cookies/cookies.css");

// ********************************************************************************************* //
// Module Implementation

/**
 * @module This class represents a <i>module</i> for Cookies panel.
 * The module supports activation (enable/disable of the Cookies panel).
 * This functionality has been introduced in Firebug 1.2 and makes possible
 * to control activity of Firebug panels in order to avoid (performance) expensive
 * features.
 */
Firebug.CookieModule = Obj.extend(Firebug.ActivableModule,
/** @lends Firebug.CookieModule */
{
    contexts: contexts,

    // Set to true if all hooks for monitoring cookies are registered; otherwise false.
    observersRegistered: false,

    /**
     * Called by Firebug when Firefox window is opened.
     *
     * @param {String} prefDomain Preference domain (e.g. extensions.firebug)
     * @param {Array} prefNames Default Firebug preference array.
     */
    initialize: function(prefDomain, prefNames)
    {
        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.CookieModule.initialize; ");

        this.traceListener = new TraceListener("cookies.", "DBG_COOKIES", true,
            "chrome://firebug/skin/cookies/trace.css");

        TraceModule.addListener(this.traceListener);

        this.panelName = panelName;
        this.description = Locale.$STR("cookies.modulemanager.description");

        Firebug.ActivableModule.initialize.apply(this, arguments);

        var permTooltip = Firebug.chrome.$("fcPermTooltip");
        permTooltip.fcEnabled = true;

        // All the necessary observers are registered by default. Even if the 
        // panel can be disabled (entirely or for a specific host) there is
        // no simple way to find out this now, as the context isn't available. 
        // All will be unregistered again in the initContext (if necessary).
        // There is no big overhead, the initContext is called just after the
        // first document request.
        //this.registerObservers();

        // Register listener for NetInfoBody (if the API is available) so,
        // a new tab (Cookies) can be appended into the Net panel request info.
        var netInfoBody = Firebug.NetMonitor.NetInfoBody;
        if ("addListener" in netInfoBody)
            netInfoBody.addListener(this.NetInfoBody);

        // Register listener within the Console panel. If document.cookie property
        // is logged, formatted output is used.
        //Firebug.Console.addListener(this.ConsoleListener);

        // Register debugger listener for providing cookie-breakpoints.
        //Firebug.Debugger.addListener(this.DebuggerListener);

        // Dynamically overlay Break on Next button in FB 1.5.1
        // There is a small decoration coming from each panel.
        var bonStack = Firebug.chrome.$("fbBreakOnNextButtonStack");
        if (bonStack)
        {
            var image = document.createElement("image");
            image.setAttribute("id", "fbBreakOnImageCookies");
            image.setAttribute("class", "fbBreakOnImage");
            image.setAttribute("src", "chrome://firebug/skin/cookies/breakOnCookieSingle.png");
            bonStack.appendChild(image);
        }

        Firebug.registerUIListener(this);
    },

    initializeUI: function()
    {
        Firebug.ActivableModule.initializeUI.apply(this, arguments);

        // Append the styleesheet to a new console popup panel introduced in Firebug 1.6
        this.addStyleSheet(null);

        Dom.collapse(Firebug.chrome.$("fbConsoleFilter-cookies"), false);
    },

    /**
     * Peforms clean up when Firebug is destroyed.
     * Called by the framework when Firebug is closed for an existing Firefox window.
     */
    shutdown: function()
    {
        this.unregisterObservers();

        // Support for trace-console customization in Firebug 1.3
        TraceModule.removeListener(this.traceListener);

        var netInfoBody = Firebug.NetMonitor.NetInfoBody;
        if ("removeListener" in netInfoBody)
            netInfoBody.removeListener(this.NetInfoBody);

        //Firebug.Console.removeListener(this.ConsoleListener);
        //Firebug.Debugger.removeListener(this.DebuggerListener);

        Firebug.unregisterUIListener(this);
    },

    registerObservers: function()
    {
        if (this.observersRegistered)
        {
            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.cookieModule.registerObservers; Observers ALREADY registered");
            return;
        }

        observerService.addObserver(HttpObserver, "http-on-modify-request", false);
        observerService.addObserver(HttpObserver, "http-on-examine-response", false);
        observerService.addObserver(PermissionObserver, "perm-changed", false);
        registerCookieObserver(CookieObserver);
        prefs.addObserver(networkPrefDomain, PrefObserver, false);

        this.observersRegistered = true;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.cookieModule.registerObservers;");
    },

    unregisterObservers: function(context)
    {
        if (!this.observersRegistered)
        {
            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.cookieModule.registerObservers; " +
                    "Observers ALREADY un-registered");
            return;
        }

        observerService.removeObserver(HttpObserver, "http-on-modify-request");
        observerService.removeObserver(HttpObserver, "http-on-examine-response");
        observerService.removeObserver(PermissionObserver, "perm-changed");
        unregisterCookieObserver(CookieObserver);
        prefs.removeObserver(networkPrefDomain, PrefObserver);

        this.observersRegistered = false;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.cookieModule.unregisterObservers;");
    },

    // Helper context
    initTempContext: function(tempContext)
    {
        tempContext.cookieTempObserver = registerCookieObserver(new CookieTempObserver(tempContext));

        // Create sub-context for cookies.
        tempContext.cookies = {};
        tempContext.cookies.activeHosts = [];
    },

    destroyTempContext: function(tempContext, context)
    {
        if (!tempContext)
            return;

        if (FBTrace.DBG_COOKIES)
        {
            FBTrace.sysout("cookies.Copy " + tempContext.events.length +
                " events to real-context.");

            var message = "cookies.Copy active hosts (";
            for (var host in tempContext.cookies.activeHosts)
                message += host + ", ";
            message = message.substring(0, message.length - 2);
            message += ") from temp context into the real context.";
            FBTrace.sysout(message, tempContext);
        }

        // Copy all active hosts on the page. In case of redirects or embedded IFrames, there
        // can be more hosts (domains) involved on the page. Cookies must be displayed for
        // all of them.
        context.cookies.activeHosts = cloneMap(tempContext.cookies.activeHosts);

        // Clone all active (received) cookies on the page.
        // This is probably not necessary, as the first cookie is received
        // in http-on-examine-response and at that time the real context
        // is already created.
        context.cookies.activeCookies = cloneMap(tempContext.cookies.activeCookies);

        // Fire all lost cookie events (those from the temp context).
        var events = tempContext.events;
        for (var i=0; i<events.length; i++) {
            var e = events[i];
            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.Fire fake cookie event: " + e.topic + ", " + e.data + "\n");
            CookieObserver.observe(e.subject, e.topic, e.data);
        }

        delete tempContext.cookies.activeHosts;
        delete tempContext.cookies.activeCookies;
        delete tempContext.cookies;

        // Unregister temporary cookie observer.
        tempContext.cookieTempObserver = unregisterCookieObserver(tempContext.cookieTempObserver);
    },

    /**
     * Called by the framework when a context is created for Firefox tab.
     * 
     *  @param {Firebug.TabContext} Context for the current Firefox tab.
     */
    initContext: function(context)
    {
        var tabId = Firebug.getTabIdForWindow(context.window);

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.INIT real context for: " + tabId + ", " +
                context.getName());

        // Create sub-context for cookies. 
        // xxxHonza: the cookies object exists within the context even if 
        // the panel is disabled.
        context.cookies = {};
        context.cookies.activeHosts = [];

        // Initialize custom path filter for this context
        context.cookies.pathFilter = "/";

        // List of breakpoints.
        context.cookies.breakpoints = new CookieBreakpointGroup();
        context.cookies.breakpoints.load(context);

        // The temp context isn't created e.g. for empty tabs, chrome pages.
        var tempContext = contexts[tabId];
        if (tempContext)
        {
            this.destroyTempContext(tempContext, context);
            delete contexts[tabId];

            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.DESTROY temporary context, tabId: " + tempContext.tabId);
        }

        // The base class must be called after the context for Cookies panel is 
        // properly initialized. The panel can be created inside this function
        // (within Firebug.ActivableModule.enablePanel), which can result in
        // calling CookiePanel.initialize method. This method directly calls
        // CookiePanel.refresh, which needs the context.cookies object ready.
        Firebug.ActivableModule.initContext.apply(this, arguments);

        // Unregister all observers if the panel is disabled.
        if (!this.isEnabled(context))
            this.unregisterObservers(context);
    },

    destroyContext: function(context) 
    {
        Firebug.ActivableModule.destroyContext.apply(this, arguments);

        if (!context.cookies)
        {
            if (FBTrace.DBG_COOKIES)
            {
                var tabId = Firebug.getTabIdForWindow(context.window);
                FBTrace.sysout("cookies.DESTROY context ERROR: No context.cookies available, tabId: " +
                    tabId + ", " + context.getName());
            }
            return;
        }

        context.cookies.breakpoints.store(context);

        for (var p in context.cookies)
            delete context.cookies[p];

        delete context.cookies;

        if (FBTrace.DBG_COOKIES)
        {
            var tabId = Firebug.getTabIdForWindow(context.window);
            FBTrace.sysout("cookies.DESTROY context, tabId: " + tabId +
                ", " + context.getName());
        }
    },

    addStyleSheet: function(panel)
    {
        // Use registration function instead (introduced in Firebug 1.6)
        if (Firebug.registerStylesheet)
            return;

        function privateAppend(doc)
        {
            // Make sure the stylesheet isn't appended twice.
            if (!Firebug.chrome.$("fcStyles", doc))
            {
                var styleSheet = createStyleSheet(doc, "chrome://firebug/skin/cookies/cookies.css");
                styleSheet.setAttribute("id", "fcStyles");
                addStyleSheet(doc, styleSheet);
            }
        }

        if (panel)
            privateAppend(panel.document)

        // Firebug 1.6 introduces another panel for console preview on other panels
        // The allows to use command line in other panels too.
        var preview = Firebug.chrome.$("fbCommandPreviewBrowser");
        if (preview)
            privateAppend(preview.contentDocument);
    },

    updateOption: function(name, value)
    {
        if (name == "consoleFilterTypes")
        {
            this.updateConsoleFilter();
        }
    },

    updateConsoleFilter: function()
    {
        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.updateConsoleFilter;");

        if (!Firebug.currentContext)
            return;

        // The panel can be disabled.
        var panel = Firebug.currentContext.getPanel("console");
        if (!panel)
            return;

        var panelNode = panel.panelNode;
        var className = "hideType-cookies";
        var filterTypes = Firebug.consoleFilterTypes;

        Css.setClass(panelNode, className);

        var positiveFilters = ["all", "cookies"];
        for (var i=0; i<positiveFilters.length; i++)
        {
            if (filterTypes.indexOf(positiveFilters[i]) >= 0)
            {
                Css.removeClass(panelNode, className);
                break;
            }
        }
    },

    showPanel: function(browser, panel)
    {
        // Update panel's toolbar
        var isCookiePanel = panel && panel.name == panelName;

        // Firebug 1.4, chrome changes.
        var chrome = browser.chrome ? browser.chrome : Firebug.chrome;

        var cookieButtons = Firebug.chrome.$("fbCookieButtons");
        Dom.collapse(cookieButtons, !isCookiePanel);

        // The console panel can be displayed sooner than the Cookies
        // panel, in such a case the Stylesheet must be ready as
        // there are cookies logs in the console.
        // Cookie table is also used within the net panel.
        if (panel && (panel.name == "console" || panel.name == "net"))
            this.addStyleSheet(panel);
    },

    watchWindow: function(context, win) 
    {
        context.window.addEventListener("beforeunload", this.onBeforeUnload, false);
    },

    onBeforeUnload: function(event) 
    {
        var view = event.target.defaultView;
        var context = TabWatcher.getContextByWindow(view);
        if (!context)
            return;

        var panel = context.getPanel(panelName, true);
        if (panel)
            panel.clear();

        if (FBTrace.DBG_COOKIES || FBTrace.DBG_ERRORS)
        {
            var tabId = Firebug.getTabIdForWindow(view);

            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.On before unload tab:  " + tabId);

            if (contexts[tabId])
            {
                delete contexts[tabId];

                if (FBTrace.DBG_COOKIES)
                    FBTrace.sysout("cookies.CookieModule.onBeforeUnload; There is a temp context leak!");
            }
        }
    },

    /**
     * Creates a new cookie in the browser.
     * This method is used by {@link EditCookie} dialog and also when a cookie is
     * pasted from the clipboard.
     *
     * @param {Cookie} Cookie object with appropriate properties. See {@link Cookie} object.
     */
    createCookie: function(cookie)
    {
        try
        {
            var uri = cookie.getURI();
            if (!uri)
                return;

            var c = cookie.cookie;

            // Fix for issue 34. The domain must be included in the cookieString if it 
            // starts with "." But don't include it otherwise, since the "." would be 
            // appended by the service.
            var host = cookie.cookie.host;
            var cookieString = cookie.toString(!(host.charAt(0) == "."));

            // Fix for issue 37: httpOnly cookies, and issue 47: Cannot change the HttpOnly flag
            // HttpOnly cookies can't be changed by setCookie string
            // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=178993
            //cookieService.setCookieString(uri, null, cookieString, null);

            // Doesn't work in FF4 (issue 95)
            //cookieService.setCookieStringFromHttp(uri, uri, null, cookieString,
            //    c.expires, null);

            //xxxHonza: in what cases the cookie should be removed?
            //var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
            //cm.remove(c.host, c.name, c.path, false);

            var isSession = c.expires ? false : true;
            var cm2 = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
            cm2.add(c.host, c.path, c.name, c.rawValue, c.isSecure, c.isHttpOnly, isSession,
                c.expires || Math.round((new Date()).getTime() / 1000 + 9999999999));

            if (FBTrace.DBG_COOKIES)
                FBTrace.sysout("cookies.createCookie: set cookie string: " + cookieString, cookie);

            // xxxHonza: this shouldn't be necessary, but sometimes the CookieObserver
            // is not triggered.
            TabWatcher.iterateContexts(function(context)
            {
                context.getPanel(panelName).refresh();
            });
        }
        catch (e)
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("cookies.createCookie: set cookie string ERROR " +
                    cookieString, e);
        }
    },

    removeCookie: function(host, name, path)
    {
        cookieManager.remove(host, name, path, false);

        // xxxHonza: this shouldn't be necessary, but sometimes the CookieObserver
        // is not triggered.
        TabWatcher.iterateContexts(function(context)
        {
            context.getPanel(panelName).refresh();
        });
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // Support for ActivableModule 1.6

    /**
     * It's just here to exists (calling base class only)
     */
    isEnabled: function(context)
    {
        return Firebug.ActivableModule.isEnabled.apply(this, arguments);
    },

    /**
     * Called when an observer (e.g. panel) is added/removed into/from the model.
     * This is the moment when the model needs to decide whether to activate.
     */
    onObserverChange: function(observer)
    {
        if (this.hasObservers())
            TabWatcher.iterateContexts(Firebug.CookieModule.registerObservers);
        else
            TabWatcher.iterateContexts(Firebug.CookieModule.unregisterObservers);

        this.setStatus();
    },

    onSuspendFirebug: function()
    {
        TabWatcher.iterateContexts(Firebug.CookieModule.unregisterObservers);

        this.setStatus();

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onSuspendFirebug");
    },

    onResumeFirebug: function(context)
    {
        if (Firebug.CookieModule.isAlwaysEnabled())
            TabWatcher.iterateContexts(Firebug.CookieModule.registerObservers);

        this.setStatus();

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onResumeFirebug");
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

    setStatus: function()
    {
        var fbStatus = Firefox.getElementById("firebugStatus");
        if (fbStatus)
        {
            if (this.hasObservers())
                fbStatus.setAttribute(panelName, "on");
            else
                fbStatus.removeAttribute(panelName);
        }
        else
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("cookies.setStatus ERROR no firebugStatus element");
        }
    },

    getMenuLabel: function(option, location)
    {
        var host = getURIHost(location);

        // In case of local files or system pages use this labels instead of host.
        // xxxHonza: the panel should be automatically disabled for local files
        // and system pages as there are no cookies associated.
        // These options shouldn't be available at all.
        if (isSystemURL(location.spec))
            host = Locale.$STR("cookies.SystemPages");
        else if (!getURIHost(location))
            host = Locale.$STR("cookies.LocalFiles");

        // Translate these two options in panel activable menu from cookies.properties
        switch (option)
        {
        case "disable-site":
            return Locale.$STRF("cookies.HostDisable", [host]);
        case "enable-site":
            return Locale.$STRF("cookies.HostEnable", [host]);
        }

        return Firebug.ActivableModule.getMenuLabel.apply(this, arguments);
    },

    // xxxHonza: This method is overriden just to provide translated strings from
    // cookies.properties file.
    openPermissions: function(event, context)
    {
        Events.cancelEvent(event);

        var browserURI = Firebug.chrome.getBrowserURI(context);
        var host = this.getHostForURI(browserURI);

        var params = {
            permissionType: this.getPrefDomain(),
            windowTitle: Locale.$STR(this.panelName + ".Permissions"),
            introText: Locale.$STR(this.panelName + ".PermissionsIntro"),
            blockVisible: true,
            sessionVisible: false,
            allowVisible: true,
            prefilledHost: host,
        };

        openWindow("Browser:Permissions", "chrome://browser/content/preferences/permissions.xul",
            "", params);
    },

    // UI Commands
    onRemoveAllShowTooltip: function(tooltip, context)
    {
        tooltip.label = Locale.$STR("cookies.removeall.tooltip");
        return true;
    },

    onRemoveAllSessionShowTooltip: function(tooltip, context)
    {
        tooltip.label = Locale.$STR("cookies.removeallsession.tooltip");
        return true;
    },

    removeCookies: function(context, filter)
    {
        var panel = context.getPanel(panelName, true);
        if (!panel)
            return;

        for (var host in context.cookies.activeHosts)
        {
            var cookieEnumerator = cookieManager.getCookiesFromHost(host);

            while (cookieEnumerator.hasMoreElements())
            {
                var cookie = cookieEnumerator.getNext().QueryInterface(Ci.nsICookie2);

                var sessionCookieToRemove = filter && filter.session && cookie.isSession;
                var remove = !filter || sessionCookieToRemove;

                if (remove && !cookies[cookie.name])
                    cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
            }
        }
    },

    onRemoveAll: function(context)
    {
        if (Options.get(removeConfirmation))
        {
            var check = {value: false};
            var flags = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_YES +  
                prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_NO;  

            if (!prompts.confirmEx(context.chrome.window, Locale.$STR("Firebug"),
                Locale.$STR("cookies.confirm.removeall"), flags, "", "", "",
                Locale.$STR("cookies.msg.Do_not_show_this_message_again"), check) == 0)
            {
                return;
            }

            // Update 'Remove Cookies' confirmation option according to the value
            // of the dialog's "do not show again" checkbox.
            Options.set(removeConfirmation, !check.value);
        }

        Firebug.CookieModule.removeCookies(context);
    },

    onRemoveAllSession: function(context)
    {
        if (Options.get(removeSessionConfirmation))
        {
            var check = {value: false};
            var flags = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_YES +  
                prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_NO;  

            if (!prompts.confirmEx(context.chrome.window, Locale.$STR("Firebug"),
                Locale.$STR("cookies.confirm.removeallsession"), flags, "", "", "",
                Locale.$STR("cookies.msg.Do_not_show_this_message_again"), check) == 0)
            {
                return;
            }

            // Update 'Remove Session Cookies' confirmation option according to the value
            // of the dialog's "do not show again" checkbox.
            Options.set(removeSessionConfirmation, !check.value)
        }

        Firebug.CookieModule.removeCookies(context, {session: true});
    },

    onCreateCookieShowTooltip: function(tooltip, context)
    {
        var host = context.window.location.host;
        tooltip.label = Locale.$STRF("cookies.createcookie.tooltip", [host]);
        return true;
    },

    onCreateCookie: function(context)
    {
        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onCreateCookie");

        // There is an excepion if the window is closed or not initialized (empty tab)
        var host;
        try {
            host = context.window.location.host
        }
        catch (err) {
            alert(Locale.$STR("cookies.message.There_is_no_active_page"));
            return;
{'fraction_non_alphanumeric': 0.11323951963456863, 'fraction_numerical': 0.0027628379871804318, 'mean_word_length': 2.2872971663841124, 'pattern_counts': {'":': 4, '<': 12, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6074142', 'n_tokens_mistral': 15270, 'n_tokens_neox': 14599, 'n_words': 3463}