nsresult
nsProfileCollector::LogOS(nsIMetricsEventItem *profile)
{
  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_STATE(properties);

  char buf[SYS_INFO_BUFFER_LENGTH];
  if (PR_GetSystemInfo(PR_SI_SYSNAME, buf, sizeof(buf)) != PR_SUCCESS) {
    MS_LOG(("Failed to get OS name"));
    return NS_ERROR_FAILURE;
  }

  properties->SetPropertyAsACString(NS_LITERAL_STRING("name"),
                                    nsDependentCString(buf));
  MS_LOG(("Logged os name=%s", buf));

  if (PR_GetSystemInfo(PR_SI_RELEASE, buf, sizeof(buf)) != PR_SUCCESS) {
    MS_LOG(("Failed to get OS version"));
    return NS_ERROR_FAILURE;
  }

  properties->SetPropertyAsACString(NS_LITERAL_STRING("version"),
                                    nsDependentCString(buf));
  MS_LOG(("Logged os version=%s", buf));

  nsresult rv = nsMetricsUtils::AddChildItem(
      profile, NS_LITERAL_STRING("os"), properties);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}
already_AddRefed<nsIMetricsEventItem>
nsProfileCollector::ExtensionEnumerator::CreateExtensionItem(
    nsIUpdateItem *extension)
{
  nsCOMPtr<nsIMetricsEventItem> extensionItem;
  mMetricsService->CreateEventItem(NS_LITERAL_STRING("extension"),
                                   getter_AddRefs(extensionItem));
  NS_ENSURE_TRUE(extensionItem, nsnull);

  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_TRUE(properties, nsnull);

  nsString id, version;
  extension->GetId(id);
  NS_ENSURE_TRUE(!id.IsEmpty(), nsnull);

  nsCString hashedID;
  nsresult rv = mMetricsService->HashUTF16(id, hashedID);
  NS_ENSURE_SUCCESS(rv, nsnull);

  properties->SetPropertyAsACString(
      NS_LITERAL_STRING("extensionid"), hashedID);
  
  extension->GetVersion(version);
  if (!version.IsEmpty()) {
    properties->SetPropertyAsAString(NS_LITERAL_STRING("version"), version);
  }
  MS_LOG(("Logged extension extensionid=%s (hashed to %s) version=%s",
          NS_ConvertUTF16toUTF8(id).get(), hashedID.get(),
          NS_ConvertUTF16toUTF8(version).get()));

  nsCString resourceID("urn:mozilla:item:");
  resourceID.Append(NS_ConvertUTF16toUTF8(id));

  nsCOMPtr<nsIRDFResource> itemResource;
  mRDFService->GetResource(resourceID, getter_AddRefs(itemResource));
  NS_ENSURE_TRUE(itemResource, nsnull);

  nsCOMPtr<nsIRDFNode> itemDisabledNode;
  mExtensionsDS->GetTarget(itemResource, mDisabledResource, PR_TRUE,
                           getter_AddRefs(itemDisabledNode));
  nsCOMPtr<nsIRDFLiteral> itemDisabledLiteral =
    do_QueryInterface(itemDisabledNode);

  if (itemDisabledLiteral) {
    const PRUnichar *value = nsnull;
    itemDisabledLiteral->GetValueConst(&value);
    if (nsDependentString(value).Equals(NS_LITERAL_STRING("true"))) {
      properties->SetPropertyAsBool(NS_LITERAL_STRING("disabled"), PR_TRUE);
      MS_LOG(("Logged extension disabled=true"));
    }
  }

  extensionItem->SetProperties(properties);

  nsIMetricsEventItem *item = nsnull;
  extensionItem.swap(item);
  return item;
}
void
nsUICommandCollector::GetEventKeyId(nsIDOMEvent *event, nsString &keyId) const
{
  // The source event will give us the original event in the case where a new
  // event was dispatched due to a command= attribute.
  nsCOMPtr<nsIDOMXULCommandEvent> commandEvent = do_QueryInterface(event);
  if (!commandEvent) {
    // nsIDOMXULCommandEvent is only in Gecko 1.8.1+
    return;
  }

  nsCOMPtr<nsIDOMEvent> sourceEvent;
  commandEvent->GetSourceEvent(getter_AddRefs(sourceEvent));
  if (!sourceEvent) {
    return;
  }

  nsCOMPtr<nsIDOMEventTarget> sourceEventTarget;
  sourceEvent->GetTarget(getter_AddRefs(sourceEventTarget));
  nsCOMPtr<nsIDOMElement> sourceElement = do_QueryInterface(sourceEventTarget);
  if (!sourceElement) {
    return;
  }

  nsString namespaceURI, localName;
  sourceElement->GetNamespaceURI(namespaceURI);
  sourceElement->GetLocalName(localName);
  if (namespaceURI.Equals(NS_LITERAL_STRING("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")) &&
      localName.Equals(NS_LITERAL_STRING("key"))) {
    sourceElement->GetAttribute(NS_LITERAL_STRING("id"), keyId);
    MS_LOG(("Key Id: %s", NS_ConvertUTF16toUTF8(keyId).get()));
  }
}
nsresult
nsUICommandCollector::HandleTabMoveEvent(nsIDOMEvent* event)
{
  PRUint32 window;
  if (NS_FAILED(GetEventWindow(event, &window))) {
    return NS_OK;
  }

  // Fill a property bag with what we want to log
  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_STATE(properties);

  nsresult rv;
  rv = properties->SetPropertyAsUint32(NS_LITERAL_STRING("window"), window);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = properties->SetPropertyAsAString(NS_LITERAL_STRING("action"),
                                        NS_LITERAL_STRING("comand"));
  NS_ENSURE_SUCCESS(rv, rv);

  // TabMove events just have a dummy target id of "TabMove_Event".
  rv = SetHashedValue(properties, NS_LITERAL_STRING("targetidhash"),
                      NS_LITERAL_STRING("TabMove_Event"));
  NS_ENSURE_SUCCESS(rv, rv);

  nsMetricsService *ms = nsMetricsService::get();
  NS_ENSURE_STATE(ms);

  rv = ms->LogEvent(NS_LITERAL_STRING("uielement"), properties);
  NS_ENSURE_SUCCESS(rv, rv);

  MS_LOG(("Successfully logged UI Event"));
  return NS_OK;
}
nsresult
nsUICommandCollector::HandlePopupShowingEvent(nsIDOMEvent* event)
{
  PRUint32 window;
  if (NS_FAILED(GetEventWindow(event, &window))) 
    return NS_OK;

  nsString targetId, targetAnonId;
  if (NS_FAILED(GetEventTargets(event, targetId, targetAnonId))) 
    return NS_OK;

  NS_ASSERTION(!targetId.IsEmpty(), "can't have an empty target id");

  if (!targetId.Equals(NS_LITERAL_STRING("identity-popup")) && !targetId.Equals(NS_LITERAL_STRING("editBookmarkPanel"))) 
    return NS_OK;

  // Fill a property bag with what we want to log
  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_STATE(properties);

  nsresult rv;
  rv = properties->SetPropertyAsUint32(NS_LITERAL_STRING("window"), window);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = properties->SetPropertyAsAString(NS_LITERAL_STRING("action"),
                                        NS_LITERAL_STRING("popupshowing"));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = SetHashedValue(properties, NS_LITERAL_STRING("targetidhash"), targetId);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!targetAnonId.IsEmpty()) 
  {
    rv = SetHashedValue(properties, NS_LITERAL_STRING("targetanonidhash"),
                        targetAnonId);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  nsMetricsService *ms = nsMetricsService::get();
  NS_ENSURE_STATE(ms);

  nsCOMPtr<nsIMetricsEventItem> item;
  ms->CreateEventItem(NS_LITERAL_STRING("uielement"), getter_AddRefs(item));
  NS_ENSURE_STATE(item);
  item->SetProperties(properties);

  // Actually log it
  rv = ms->LogEvent(item);
  NS_ENSURE_SUCCESS(rv, rv);

  MS_LOG(("Successfully logged UI popupshowing Event"));
  return NS_OK;
}
nsresult
nsProfileCollector::LogMemory(nsIMetricsEventItem *profile)
{
  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_STATE(properties);

  PRUint64 size = PR_GetPhysicalMemorySize();
  if (size == 0) {
    MS_LOG(("Failed to get physical memory size"));
    return NS_ERROR_FAILURE;
  }

  PRUint64 sizeMB = size >> 20;
  properties->SetPropertyAsUint64(NS_LITERAL_STRING("mb"), sizeMB);
  MS_LOG(("Logged memory mb=%ull", sizeMB));

  nsresult rv = nsMetricsUtils::AddChildItem(
      profile, NS_LITERAL_STRING("memory"), properties);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}
void
nsUICommandCollector::RemoveEventListeners(nsIDOMEventTarget *window)
{
  for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(kEvents); ++i) {
    // Using NS_LITERAL_STRING in const data is not allowed, so convert here.
    // This is not performance-sensitive.
    nsresult rv;
    rv = window->RemoveEventListener(NS_ConvertASCIItoUTF16(kEvents[i].event),
                                     this, PR_TRUE);
    if (NS_FAILED(rv)) {
      MS_LOG(("Couldn't remove event listener %s", kEvents[i]));
    }
  }
}
nsresult
nsProfileCollector::LogCPU(nsIMetricsEventItem *profile)
{
  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_STATE(properties);

  char buf[SYS_INFO_BUFFER_LENGTH];
  if (PR_GetSystemInfo(PR_SI_ARCHITECTURE, buf, sizeof(buf)) != PR_SUCCESS) {
    MS_LOG(("Failed to get architecture"));
    return NS_ERROR_FAILURE;
  }

  properties->SetPropertyAsACString(NS_LITERAL_STRING("arch"),
                                    nsDependentCString(buf));
  MS_LOG(("Logged CPU arch=%s", buf));

  nsresult rv = nsMetricsUtils::AddChildItem(
      profile, NS_LITERAL_STRING("cpu"), properties);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}
nsresult
nsProfileCollector::LogDisplay(nsIMetricsEventItem *profile)
{
  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_STATE(properties);

  nsCOMPtr<nsIScreenManager> screenManager =
    do_GetService("@mozilla.org/gfx/screenmanager;1");
  NS_ENSURE_STATE(screenManager);

  nsCOMPtr<nsIScreen> primaryScreen;
  screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen));
  NS_ENSURE_STATE(primaryScreen);

  PRInt32 left, top, width, height;
  nsresult rv = primaryScreen->GetRect(&left, &top, &width, &height);
  NS_ENSURE_SUCCESS(rv, rv);

  properties->SetPropertyAsInt32(NS_LITERAL_STRING("xsize"), width);
  properties->SetPropertyAsInt32(NS_LITERAL_STRING("ysize"), height);
  MS_LOG(("Logged display xsize=%d ysize=%d", width, height));

  PRUint32 numScreens = 0;
  if (NS_SUCCEEDED(screenManager->GetNumberOfScreens(&numScreens))) {
    properties->SetPropertyAsUint32(NS_LITERAL_STRING("screens"), numScreens);
    MS_LOG(("Logged display screens=%d", numScreens));
  } else {
    MS_LOG(("Could not get number of screens"));
  }

  rv = nsMetricsUtils::AddChildItem(profile,
                                    NS_LITERAL_STRING("display"), properties);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}
nsresult
nsUICommandCollector::GetEventWindow(nsIDOMEvent *event,
                                     PRUint32 *window) const
{
  nsCOMPtr<nsIDOMEventTarget> target;
  event->GetTarget(getter_AddRefs(target));
  nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(target);
  if (!targetNode) {
    MS_LOG(("Warning: couldn't get window id because target is not a node"));
    return NS_ERROR_FAILURE;
  }

  *window = nsMetricsUtils::FindWindowForNode(targetNode);
  return NS_OK;
}
/* static */
PLDHashOperator nsUICommandCollector::RemoveCommandEventListener(
const nsIDOMWindow* key, PRUint32 windowID, void* userArg)
{
  nsCOMPtr<nsIDOMEventTarget> windowTarget =
    do_QueryInterface(const_cast<nsIDOMWindow *>(key));
  if (!windowTarget) {
    MS_LOG(("Error casting domeventtarget"));
    return PL_DHASH_NEXT;
  }

  nsUICommandCollector* collector = static_cast<nsUICommandCollector*>
                                               (userArg);
  collector->RemoveEventListeners(windowTarget);
  return PL_DHASH_NEXT;
}
void
nsUICommandCollector::AddEventListeners(nsIDOMEventTarget *window)
{
  for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(kEvents); ++i) {
    // Attach a capturing event listener to the window.
    // Use capturing instead of bubbling so that we still record
    // the event even if propagation is canceled for some reason.

    // Using NS_LITERAL_STRING in const data is not allowed, so convert here.
    // This is not performance-sensitive.
    nsresult rv;
    rv = window->AddEventListener(NS_ConvertASCIItoUTF16(kEvents[i].event),
                                  this, PR_TRUE);
    if (NS_FAILED(rv)) {
      MS_LOG(("Couldn't add event listener %s", kEvents[i]));
    }
  }
}
// nsIDOMEventListener
NS_IMETHODIMP
nsUICommandCollector::HandleEvent(nsIDOMEvent* event)
{
  // First check that this is an event type that we expect.
  // If so, call the appropriate handler method.
  nsString type;
  nsresult rv = event->GetType(type);
  NS_ENSURE_SUCCESS(rv, rv);

  for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(kEvents); ++i) {
    if (NS_ConvertASCIItoUTF16(kEvents[i].event).Equals(type)) {
      return (this->*(kEvents[i].handler))(event);
    }
  }

  MS_LOG(("UICommandCollector: Unexpected event type %s received",
          NS_ConvertUTF16toUTF8(type).get()));
  return NS_ERROR_UNEXPECTED;
}
already_AddRefed<nsIMetricsEventItem>
nsProfileCollector::PluginEnumerator::CreatePluginItem(nsIDOMPlugin *plugin)
{
  nsCOMPtr<nsIMetricsEventItem> pluginItem;
  mMetricsService->CreateEventItem(NS_LITERAL_STRING("plugin"),
                                   getter_AddRefs(pluginItem));
  NS_ENSURE_TRUE(pluginItem, nsnull);

  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_TRUE(properties, nsnull);

  nsString name, filename;
  plugin->GetName(name);
  plugin->GetFilename(filename);

  nsCString hashedName, hashedFilename;
  nsresult rv = mMetricsService->HashUTF16(name, hashedName);
  NS_ENSURE_SUCCESS(rv, nsnull);
  rv = mMetricsService->HashUTF16(filename, hashedFilename);
  NS_ENSURE_SUCCESS(rv, nsnull);

  properties->SetPropertyAsACString(NS_LITERAL_STRING("name"), hashedName);
  properties->SetPropertyAsACString(NS_LITERAL_STRING("filename"),
                                    hashedFilename);
  MS_LOG(("Logged plugin name=%s (hashed to %s) filename=%s (hashed to %s)",
          NS_ConvertUTF16toUTF8(name).get(), hashedName.get(),
          NS_ConvertUTF16toUTF8(filename).get(), hashedFilename.get()));


  // TODO(bryner): log the version, if there's a way to find it

  pluginItem->SetProperties(properties);

  nsIMetricsEventItem *item = nsnull;
  pluginItem.swap(item);
  return item;
}
nsresult
nsUICommandCollector::GetEventTargets(nsIDOMEvent *event,
                                      nsString &targetId,
                                      nsString &targetAnonId) const
{
  // This code deals with both anonymous and explicit (non-anonymous) content.
  //
  // For explicit content, we just return the id of the event target in
  // targetId, and leave targetAnonId empty.  If there is no id, then
  // we return failure.
  //
  // For anonymous content, we return the id of the event target (after
  // retargeting), in targetId.  We return the anonid of the event's
  // originalTarget in targetAnonId, so that XBL child elements can be
  // distinguished.  If there is no anonid, then we return failure.
  // Note that the originalTarget is set after text node retargting, but
  // before any XBL retargeting.
  //
  // We assume that if the originalTarget has no id, we're dealing with
  // anonymous content (this isn't always true, but it's good enough for what
  // this code does).

  nsCOMPtr<nsIDOMNSEvent> nsEvent = do_QueryInterface(event);
  NS_ENSURE_STATE(nsEvent);

  nsCOMPtr<nsIDOMEventTarget> originalTarget;
  nsEvent->GetOriginalTarget(getter_AddRefs(originalTarget));
  NS_ENSURE_STATE(originalTarget);

  nsString origElementId;
  nsCOMPtr<nsIDOMElement> origElement(do_QueryInterface(originalTarget));
  if (origElement) {
    origElement->GetAttribute(NS_LITERAL_STRING("id"), origElementId);
    origElement->GetAttribute(NS_LITERAL_STRING("anonid"), targetAnonId);
  }

  nsCOMPtr<nsIDOMEventTarget> target;
  event->GetTarget(getter_AddRefs(target));
  NS_ENSURE_STATE(target);

  nsCOMPtr<nsIDOMElement> targetElement(do_QueryInterface(target));
  if (targetElement) {
    targetElement->GetAttribute(NS_LITERAL_STRING("id"), targetId);
  }

  MS_LOG(("Original Target Id: %s, Original Target Anonid: %s, Target Id: %s",
          NS_ConvertUTF16toUTF8(origElementId).get(),
          NS_ConvertUTF16toUTF8(targetAnonId).get(),
          NS_ConvertUTF16toUTF8(targetId).get()));

  if (targetId.IsEmpty()) {
    // There's nothing useful to log in this case -- even if we have an anonid,
    // it's not possible to determine its position in the document.
    MS_LOG(("Warning: skipping logging because of empty target ID"));
    return NS_ERROR_FAILURE;
  }

  if (origElementId.IsEmpty()) {
    // We're dealing with anonymous content, so don't continue if we didn't
    // find an anonid.
    if (targetAnonId.IsEmpty()) {
      MS_LOG(("Warning: skipping logging because of empty anonid"));
      return NS_ERROR_FAILURE;
    }
  } else {
    // We're dealing with normal explicit content, so don't return an anonid.
    targetAnonId.SetLength(0);
  }

  return NS_OK;
}
nsresult
nsProfileCollector::LogInstall(nsIMetricsEventItem *profile)
{
  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_STATE(properties);

  nsCOMPtr<nsIXULAppInfo> appInfo =
    do_GetService(XULAPPINFO_SERVICE_CONTRACTID);
  NS_ENSURE_STATE(appInfo);

  nsCString buildID;
  appInfo->GetAppBuildID(buildID);
  properties->SetPropertyAsACString(NS_LITERAL_STRING("buildid"), buildID);
  MS_LOG(("Logged install buildid=%s", buildID.get()));

  nsCOMPtr<nsIExtensionManager> em = do_GetService("@mozilla.org/extensions/manager;1");
  NS_ENSURE_STATE(em);

  nsCOMPtr<nsIUpdateItem> item;
  nsresult rv = em->GetItemForID(NS_LITERAL_STRING("*****@*****.**"), getter_AddRefs(item));
  NS_ENSURE_SUCCESS(rv, rv);

  // get the metrics extension version
  nsAutoString extversion;
  rv = item->GetVersion(extversion);
  NS_ENSURE_SUCCESS(rv, rv);
  properties->SetPropertyAsAString(NS_LITERAL_STRING("extversion"), extversion);
  
  MS_LOG(("Logged install extversion=%s", NS_ConvertUTF16toUTF8(extversion).get()));

  // get the application version
  nsCString appversion;
  appInfo->GetVersion(appversion);
  properties->SetPropertyAsACString(NS_LITERAL_STRING("appversion"), appversion);

  MS_LOG(("Logged install appversion=%s", appversion.get()));

  // get the application locale
  nsCOMPtr<nsILocaleService> ls = do_GetService(NS_LOCALESERVICE_CONTRACTID);
  NS_ENSURE_STATE(ls);

  nsCOMPtr<nsILocale> locale(nsnull);
  rv = ls->GetApplicationLocale(getter_AddRefs(locale));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString l;
  rv = locale->GetCategory(NS_LITERAL_STRING("NSILOCALE_CTYPE"), l);
  NS_ENSURE_SUCCESS(rv, rv);
  properties->SetPropertyAsAString(NS_LITERAL_STRING("locale"), l);

  MS_LOG(("Logged install locale=%s", NS_ConvertUTF16toUTF8(l).get()));

  // The file defaults/pref/channel-prefs.js is exlucded from any
  // security update, so we can use its creation time as an indicator
  // of when this installation was performed.

  nsCOMPtr<nsIFile> prefsDirectory;
  NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR,
                         getter_AddRefs(prefsDirectory));

  nsCOMPtr<nsILocalFile> channelPrefs = do_QueryInterface(prefsDirectory);
  NS_ENSURE_STATE(channelPrefs);

  rv = channelPrefs->Append(NS_LITERAL_STRING("channel-prefs.js"));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCString nativePath;
  channelPrefs->GetNativePath(nativePath);
  PRFileInfo64 fileInfo;
  if (PR_GetFileInfo64(nativePath.get(), &fileInfo) == PR_SUCCESS) {
    // Convert the time to seconds since the epoch
    PRInt64 installTime = fileInfo.creationTime / PR_USEC_PER_SEC;

    static const int kSecondsPerDay = 60 * 60 * 24;

    // Round down to the nearest full day
    installTime = ((installTime / kSecondsPerDay) * kSecondsPerDay);
    properties->SetPropertyAsInt64(NS_LITERAL_STRING("installdate"),
                                   installTime);
    MS_LOG(("Logged install installdate=%lld", installTime));
  }

  // TODO: log default= based on default-browser selection
  properties->SetPropertyAsBool(NS_LITERAL_STRING("default"), PR_TRUE);

  rv = nsMetricsUtils::AddChildItem(profile,
                                    NS_LITERAL_STRING("install"), properties);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}
nsresult
nsUICommandCollector::HandleCommandEvent(nsIDOMEvent* event)
{
  PRUint32 window;
  if (NS_FAILED(GetEventWindow(event, &window))) {
    return NS_OK;
  }

  nsString targetId, targetAnonId;
  if (NS_FAILED(GetEventTargets(event, targetId, targetAnonId))) {
    return NS_OK;
  }
  NS_ASSERTION(!targetId.IsEmpty(), "can't have an empty target id");

  nsString keyId;
  GetEventKeyId(event, keyId);

  // Fill a property bag with what we want to log
  nsCOMPtr<nsIWritablePropertyBag2> properties;
  nsMetricsUtils::NewPropertyBag(getter_AddRefs(properties));
  NS_ENSURE_STATE(properties);

  nsresult rv;
  rv = properties->SetPropertyAsUint32(NS_LITERAL_STRING("window"), window);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = properties->SetPropertyAsAString(NS_LITERAL_STRING("action"),
                                        NS_LITERAL_STRING("command"));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = SetHashedValue(properties, NS_LITERAL_STRING("targetidhash"), targetId);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!targetAnonId.IsEmpty()) {
    rv = SetHashedValue(properties, NS_LITERAL_STRING("targetanonidhash"),
                        targetAnonId);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  if (!keyId.IsEmpty()) {
    rv = SetHashedValue(properties, NS_LITERAL_STRING("keyidhash"), keyId);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  nsMetricsService *ms = nsMetricsService::get();
  NS_ENSURE_STATE(ms);

  nsCOMPtr<nsIMetricsEventItem> item;
  ms->CreateEventItem(NS_LITERAL_STRING("uielement"), getter_AddRefs(item));
  NS_ENSURE_STATE(item);
  item->SetProperties(properties);

  // Capture extra bookmark state onto the event if the target is a bookmark.
  rv = LogBookmarkInfo(targetId, item);
  NS_ENSURE_SUCCESS(rv, rv);

  // Actually log it
  rv = ms->LogEvent(item);
  NS_ENSURE_SUCCESS(rv, rv);

  MS_LOG(("Successfully logged UI Event"));
  return NS_OK;
}
NS_IMETHODIMP
nsLoadCollector::OnStateChange(nsIWebProgress *webProgress,
                               nsIRequest *request,
                               PRUint32 flags,
                               nsresult status)
{
  NS_ASSERTION(flags & STATE_IS_DOCUMENT,
               "incorrect state change notification");

#ifdef PR_LOGGING
  if (MS_LOG_ENABLED()) {
    nsCString name;
    request->GetName(name);

    MS_LOG(("LoadCollector: progress = %p, request = %p [%s], flags = %x, status = %x",
            webProgress, request, name.get(), flags, status));
  }
#endif

  nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
  if (!channel) {
    // We don't care about non-channel requests
    return NS_OK;
  }

  nsresult rv;
  if (flags & STATE_START) {
    RequestEntry entry;
    NS_ASSERTION(!mRequestMap.Get(request, &entry), "duplicate STATE_START");

    nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webProgress);
    nsCOMPtr<nsIDOMWindow> window = do_GetInterface(docShell);
    if (!window) {
      // We don't really care about windowless loads
      return NS_OK;
    }

    rv = nsMetricsUtils::NewPropertyBag(getter_AddRefs(entry.properties));
    NS_ENSURE_SUCCESS(rv, rv);
    nsIWritablePropertyBag2 *props = entry.properties;

    rv = props->SetPropertyAsUint32(NS_LITERAL_STRING("window"),
                                    nsMetricsService::GetWindowID(window));
    NS_ENSURE_SUCCESS(rv, rv);

    if (flags & STATE_RESTORING) {
      rv = props->SetPropertyAsBool(NS_LITERAL_STRING("bfCacheHit"), PR_TRUE);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    nsString origin;
    PRUint32 loadType;
    docShell->GetLoadType(&loadType);

    switch (loadType) {
    case LOAD_NORMAL:
    case LOAD_NORMAL_REPLACE:
    case LOAD_BYPASS_HISTORY:
      origin = NS_LITERAL_STRING("typed");
      break;
    case LOAD_NORMAL_EXTERNAL:
      origin = NS_LITERAL_STRING("external");
      break;
    case LOAD_HISTORY:
      origin = NS_LITERAL_STRING("session-history");
      break;
    case LOAD_RELOAD_NORMAL:
    case LOAD_RELOAD_BYPASS_CACHE:
    case LOAD_RELOAD_BYPASS_PROXY:
    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
    case LOAD_RELOAD_CHARSET_CHANGE:
      origin = NS_LITERAL_STRING("reload");
      break;
    case LOAD_LINK:
      origin = NS_LITERAL_STRING("link");
      break;
    case LOAD_REFRESH:
      origin = NS_LITERAL_STRING("refresh");
      break;
    default:
      break;
    }
    if (!origin.IsEmpty()) {
      rv = props->SetPropertyAsAString(NS_LITERAL_STRING("origin"), origin);
      NS_ENSURE_SUCCESS(rv, rv);
    }
    entry.startTime = PR_Now();
    NS_ENSURE_TRUE(mRequestMap.Put(request, entry), NS_ERROR_OUT_OF_MEMORY);
  } else if (flags & STATE_STOP) {
    RequestEntry entry;
    if (mRequestMap.Get(request, &entry)) {
      mRequestMap.Remove(request);

      // Log a <document action="load"> event
      nsIWritablePropertyBag2 *props = entry.properties;
      rv = props->SetPropertyAsACString(NS_LITERAL_STRING("action"),
                                        NS_LITERAL_CSTRING("load"));
      NS_ENSURE_SUCCESS(rv, rv);
      
      // Compute the load time now that we have the end time.
      PRInt64 loadTime = (PR_Now() - entry.startTime) / PR_USEC_PER_MSEC;
      rv = props->SetPropertyAsUint64(NS_LITERAL_STRING("loadtime"), loadTime);
      NS_ENSURE_SUCCESS(rv, rv);

      MemUsage mu;
      if (GetMemUsage(&mu)) {
        rv = props->SetPropertyAsUint64(NS_LITERAL_STRING("memtotal"), mu.total);
        NS_ENSURE_SUCCESS(rv, rv);
        rv = props->SetPropertyAsUint64(NS_LITERAL_STRING("memresident"), mu.resident);
        NS_ENSURE_SUCCESS(rv, rv);
      }

      // Look up the document id, or assign a new one
      nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webProgress);
      nsCOMPtr<nsIDOMWindow> window = do_GetInterface(docShell);
      if (!window) {
        MS_LOG(("Couldn't get window"));
        return NS_ERROR_UNEXPECTED;
      }

      nsCOMPtr<nsIDOMDocument> domDoc;
      window->GetDocument(getter_AddRefs(domDoc));
      nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
      if (!doc) {
        MS_LOG(("Couldn't get document"));
        return NS_ERROR_UNEXPECTED;
      }

      nsCOMPtr<nsIDocShellTreeItem> item = do_QueryInterface(docShell);
      NS_ENSURE_STATE(item);
      PRBool subframe = nsMetricsUtils::IsSubframe(item);
      if (subframe) {
        rv = props->SetPropertyAsBool(NS_LITERAL_STRING("subframe"), PR_TRUE);
        NS_ENSURE_SUCCESS(rv, rv);
      }

      nsMetricsService *ms = nsMetricsService::get();
      DocumentEntry docEntry;
      if (!mDocumentMap.Get(doc, &docEntry)) {
        docEntry.docID = mNextDocID++;
        docEntry.subframe = subframe;

        if (!ms->WindowMap().Get(window, &docEntry.windowID)) {
          MS_LOG(("Window not in the window map"));
          return NS_ERROR_UNEXPECTED;
        }

        NS_ENSURE_TRUE(mDocumentMap.Put(doc, docEntry),
                       NS_ERROR_OUT_OF_MEMORY);
      }
      doc->AddObserver(this);  // set up to log the document destroy

      rv = props->SetPropertyAsUint32(NS_LITERAL_STRING("docid"),
                                      docEntry.docID);
      NS_ENSURE_SUCCESS(rv, rv);

      // If this was a load of a chrome document, hash the URL of the document
      // so it can be identified.

      nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
      if (channel) {
        nsCOMPtr<nsIURI> uri;
        channel->GetURI(getter_AddRefs(uri));
        if (uri) {
          PRBool isChrome = PR_FALSE;
          uri->SchemeIs("chrome", &isChrome);
          if (isChrome) {
            nsCString spec;
            uri->GetSpec(spec);

            nsCString hashedSpec;
            rv = ms->HashUTF8(spec, hashedSpec);
            NS_ENSURE_SUCCESS(rv, rv);

            rv = props->SetPropertyAsACString(NS_LITERAL_STRING("urlhash"),
                                              hashedSpec);
            NS_ENSURE_SUCCESS(rv, rv);
          }
        }
      }

      rv = ms->LogEvent(NS_LITERAL_STRING("document"), props);
      NS_ENSURE_SUCCESS(rv, rv);
    } else {
      NS_WARNING("STATE_STOP without STATE_START");
    }
  }

  return NS_OK;
}