bool
ServiceWorkerManagerParent::RecvRegister(
                                     const ServiceWorkerRegistrationData& aData)
{
  AssertIsInMainProcess();
  AssertIsOnBackgroundThread();

  // Basic validation.
  if (aData.scope().IsEmpty() ||
      aData.scriptSpec().IsEmpty() ||
      aData.principal().type() == PrincipalInfo::TNullPrincipalInfo ||
      aData.principal().type() == PrincipalInfo::TSystemPrincipalInfo) {
    return false;
  }

  RefPtr<RegisterServiceWorkerCallback> callback =
    new RegisterServiceWorkerCallback(aData, mID);

  RefPtr<ContentParent> parent =
    BackgroundParent::GetContentParent(Manager());

  // If the ContentParent is null we are dealing with a same-process actor.
  if (!parent) {
    callback->Run();
    return true;
  }

  RefPtr<CheckPrincipalWithCallbackRunnable> runnable =
    new CheckPrincipalWithCallbackRunnable(parent.forget(), aData.principal(),
                                           callback);
  nsresult rv = NS_DispatchToMainThread(runnable);
  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));

  return true;
}
void
ServiceWorkerRegistrar::UnregisterServiceWorker(
    const PrincipalInfo& aPrincipalInfo,
    const nsACString& aScope)
{
    AssertIsOnBackgroundThread();

    if (mShuttingDown) {
        NS_WARNING("Failed to unregister a serviceWorker during shutting down.");
        return;
    }

    bool deleted = false;

    {
        MonitorAutoLock lock(mMonitor);
        MOZ_ASSERT(mDataLoaded);

        ServiceWorkerRegistrationData tmp;
        tmp.principal() = aPrincipalInfo;
        tmp.scope() = aScope;

        for (uint32_t i = 0; i < mData.Length(); ++i) {
            if (Equivalent(tmp, mData[i])) {
                mData.RemoveElementAt(i);
                deleted = true;
                break;
            }
        }
    }

    if (deleted) {
        ScheduleSaveData();
    }
}
TEST(ServiceWorkerRegistrar, TestWriteData)
{
  {
    RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;

    nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();

    for (int i = 0; i < 10; ++i) {
      ServiceWorkerRegistrationData* d = data.AppendElement();

      nsAutoCString spec;
      spec.AppendPrintf("spec write %d", i);
      d->principal() = mozilla::ipc::ContentPrincipalInfo(mozilla::PrincipalOriginAttributes(i, i % 2), spec);
      d->scope().AppendPrintf("scope write %d", i);
      d->currentWorkerURL().AppendPrintf("currentWorkerURL write %d", i);
      d->cacheName().AppendPrintf("cacheName write %d", i);
    }

    nsresult rv = swr->TestWriteData();
    ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
  }

  RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;

  nsresult rv = swr->TestReadData();
  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";

  const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
  ASSERT_EQ((uint32_t)10, data.Length()) << "10 entries should be found";

  for (int i = 0; i < 10; ++i) {
    nsAutoCString test;

    ASSERT_EQ(data[i].principal().type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
    const mozilla::ipc::ContentPrincipalInfo& cInfo = data[i].principal();

    mozilla::PrincipalOriginAttributes attrs(i, i % 2);
    nsAutoCString suffix, expectSuffix;
    attrs.CreateSuffix(expectSuffix);
    cInfo.attrs().CreateSuffix(suffix);

    ASSERT_STREQ(expectSuffix.get(), suffix.get());

    test.AppendPrintf("scope write %d", i);
    ASSERT_STREQ(test.get(), cInfo.spec().get());

    test.Truncate();
    test.AppendPrintf("scope write %d", i);
    ASSERT_STREQ(test.get(), data[i].scope().get());

    test.Truncate();
    test.AppendPrintf("currentWorkerURL write %d", i);
    ASSERT_STREQ(test.get(), data[i].currentWorkerURL().get());

    test.Truncate();
    test.AppendPrintf("cacheName write %d", i);
    ASSERT_STREQ(test.get(), NS_ConvertUTF16toUTF8(data[i].cacheName()).get());
  }
}
Example #4
0
TEST(ServiceWorkerRegistrar, TestDedupeWrite)
{
  {
    RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;

    for (int i = 0; i < 10; ++i) {
      ServiceWorkerRegistrationData reg;

      reg.scope() = NS_LITERAL_CSTRING("scope write dedupe");
      reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
      reg.cacheName() =
        NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));

      nsAutoCString spec;
      spec.AppendPrintf("spec write dedupe/%d", i);
      reg.principal() =
        mozilla::ipc::ContentPrincipalInfo(mozilla::PrincipalOriginAttributes(0, false), spec);

      swr->TestRegisterServiceWorker(reg);
    }

    nsresult rv = swr->TestWriteData();
    ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
  }

  RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;

  nsresult rv = swr->TestReadData();
  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";

  // Duplicate entries should be removed.
  const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
  ASSERT_EQ((uint32_t)1, data.Length()) << "1 entry should be found";

  ASSERT_EQ(data[0].principal().type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
  const mozilla::ipc::ContentPrincipalInfo& cInfo = data[0].principal();

  mozilla::PrincipalOriginAttributes attrs(0, false);
  nsAutoCString suffix, expectSuffix;
  attrs.CreateSuffix(expectSuffix);
  cInfo.attrs().CreateSuffix(suffix);

  // Last entry passed to RegisterServiceWorkerInternal() should overwrite
  // previous values.  So expect "9" in values here.
  ASSERT_STREQ(expectSuffix.get(), suffix.get());
  ASSERT_STREQ("scope write dedupe", cInfo.spec().get());
  ASSERT_STREQ("scope write dedupe", data[0].scope().get());
  ASSERT_STREQ("currentWorkerURL write 9", data[0].currentWorkerURL().get());
  ASSERT_STREQ("cacheName write 9",
               NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
}
void ServiceWorkerManagerService::PropagateRegistration(
    uint64_t aParentID, ServiceWorkerRegistrationData& aData) {
  AssertIsOnBackgroundThread();

  if (ServiceWorkerParentInterceptEnabled()) {
    return;
  }

  DebugOnly<bool> parentFound = false;
  for (auto iter = mAgents.Iter(); !iter.Done(); iter.Next()) {
    RefPtr<ServiceWorkerManagerParent> parent = iter.Get()->GetKey();
    MOZ_ASSERT(parent);

    if (parent->ID() != aParentID) {
      Unused << parent->SendNotifyRegister(aData);
#ifdef DEBUG
    } else {
      parentFound = true;
#endif
    }
  }

  // Send permissions fot the newly registered service worker to all of the
  // content processes.
  PrincipalInfo pi = aData.principal();
  NS_DispatchToMainThread(NS_NewRunnableFunction(
      "dom::ServiceWorkerManagerService::PropagateRegistration", [pi]() {
        nsTArray<ContentParent*> cps;
        ContentParent::GetAll(cps);
        for (auto* cp : cps) {
          nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(pi);
          if (principal) {
            cp->TransmitPermissionsForPrincipal(principal);
          }
        }
      }));

#ifdef DEBUG
  MOZ_ASSERT(parentFound);
#endif
}
nsresult
ServiceWorkerRegistrar::ReadData()
{
  // We cannot assert about the correct thread because normally this method
  // runs on a IO thread, but in gTests we call it from the main-thread.

  MOZ_ASSERT(mProfileDir);

  nsCOMPtr<nsIFile> file;
  nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  bool exists;
  rv = file->Exists(&exists);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (!exists) {
    return NS_OK;
  }

  nsCOMPtr<nsIInputStream> stream;
  rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(stream);
  MOZ_ASSERT(lineInputStream);

  nsAutoCString line;
  bool hasMoreLines;
  rv = lineInputStream->ReadLine(line, &hasMoreLines);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // The file is corrupted ?
  // FIXME: in case we implement a version 2, we should inform the user using
  // the console about this issue.
  if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_VERSION)) {
    return NS_ERROR_FAILURE;
  }

  while (hasMoreLines) {
    ServiceWorkerRegistrationData* entry = mData.AppendElement();

#define GET_LINE(x)                                   \
    rv = lineInputStream->ReadLine(x, &hasMoreLines); \
    if (NS_WARN_IF(NS_FAILED(rv))) {                  \
      return rv;                                      \
    }                                                 \
    if (NS_WARN_IF(!hasMoreLines)) {                  \
      return NS_ERROR_FAILURE;                        \
    }

    GET_LINE(line);

    if (line.EqualsLiteral(SERVICEWORKERREGISTRAR_SYSTEM_PRINCIPAL)) {
      entry->principal() = mozilla::ipc::SystemPrincipalInfo();
    } else {
      if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_CONTENT_PRINCIPAL)) {
        return NS_ERROR_FAILURE;
      }

      GET_LINE(line);

      uint32_t appId = line.ToInteger(&rv);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      GET_LINE(line);

      if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
          !line.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
        return NS_ERROR_FAILURE;
      }

      bool isInBrowserElement = line.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);

      GET_LINE(line);
      entry->principal() =
        mozilla::ipc::ContentPrincipalInfo(appId, isInBrowserElement,
                                           line);
    }

    GET_LINE(entry->scope());
    GET_LINE(entry->scriptSpec());
    GET_LINE(entry->currentWorkerURL());

#undef GET_LINE

    rv = lineInputStream->ReadLine(line, &hasMoreLines);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_TERMINATOR)) {
      return NS_ERROR_FAILURE;
    }
  }

  return NS_OK;
}
nsresult
ServiceWorkerRegistrar::ReadData()
{
    // We cannot assert about the correct thread because normally this method
    // runs on a IO thread, but in gTests we call it from the main-thread.

    MOZ_ASSERT(mProfileDir);

    nsCOMPtr<nsIFile> file;
    nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
    if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
    }

    rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
    if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
    }

    bool exists;
    rv = file->Exists(&exists);
    if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
    }

    if (!exists) {
        return NS_OK;
    }

    nsCOMPtr<nsIInputStream> stream;
    rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
    if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
    }

    nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(stream);
    MOZ_ASSERT(lineInputStream);

    nsAutoCString version;
    bool hasMoreLines;
    rv = lineInputStream->ReadLine(version, &hasMoreLines);
    if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
    }

    if (!IsSupportedVersion(version)) {
        nsContentUtils::LogMessageToConsole(nsPrintfCString(
                                                "Unsupported service worker registrar version: %s", version.get()).get());
        return NS_ERROR_FAILURE;
    }

    nsTArray<ServiceWorkerRegistrationData> tmpData;

    bool overwrite = false;
    bool dedupe = false;
    while (hasMoreLines) {
        ServiceWorkerRegistrationData* entry = tmpData.AppendElement();

#define GET_LINE(x)                                   \
    rv = lineInputStream->ReadLine(x, &hasMoreLines); \
    if (NS_WARN_IF(NS_FAILED(rv))) {                  \
      return rv;                                      \
    }                                                 \
    if (NS_WARN_IF(!hasMoreLines)) {                  \
      return NS_ERROR_FAILURE;                        \
    }

        nsAutoCString line;
        nsAutoCString unused;
        if (version.EqualsLiteral(SERVICEWORKERREGISTRAR_VERSION)) {
            nsAutoCString suffix;
            GET_LINE(suffix);

            PrincipalOriginAttributes attrs;
            if (!attrs.PopulateFromSuffix(suffix)) {
                return NS_ERROR_INVALID_ARG;
            }

            GET_LINE(entry->scope());

            entry->principal() =
                mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope());

            GET_LINE(entry->currentWorkerURL());

            nsAutoCString cacheName;
            GET_LINE(cacheName);
            CopyUTF8toUTF16(cacheName, entry->cacheName());
        } else if (version.EqualsLiteral("3")) {
            overwrite = true;
            dedupe = true;

            nsAutoCString suffix;
            GET_LINE(suffix);

            PrincipalOriginAttributes attrs;
            if (!attrs.PopulateFromSuffix(suffix)) {
                return NS_ERROR_INVALID_ARG;
            }

            // principal spec is no longer used; we use scope directly instead
            GET_LINE(unused);

            GET_LINE(entry->scope());

            entry->principal() =
                mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope());

            GET_LINE(entry->currentWorkerURL());

            nsAutoCString cacheName;
            GET_LINE(cacheName);
            CopyUTF8toUTF16(cacheName, entry->cacheName());
        } else if (version.EqualsLiteral("2")) {
            overwrite = true;
            dedupe = true;

            nsAutoCString suffix;
            GET_LINE(suffix);

            PrincipalOriginAttributes attrs;
            if (!attrs.PopulateFromSuffix(suffix)) {
                return NS_ERROR_INVALID_ARG;
            }

            // principal spec is no longer used; we use scope directly instead
            GET_LINE(unused);

            GET_LINE(entry->scope());

            entry->principal() =
                mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope());

            // scriptSpec is no more used in latest version.
            GET_LINE(unused);

            GET_LINE(entry->currentWorkerURL());

            nsAutoCString cacheName;
            GET_LINE(cacheName);
            CopyUTF8toUTF16(cacheName, entry->cacheName());

            // waitingCacheName is no more used in latest version.
            GET_LINE(unused);
        } else {
            MOZ_ASSERT_UNREACHABLE("Should never get here!");
        }

#undef GET_LINE

        rv = lineInputStream->ReadLine(line, &hasMoreLines);
        if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
        }

        if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_TERMINATOR)) {
            return NS_ERROR_FAILURE;
        }
    }

    stream->Close();

    // Copy data over to mData.
    for (uint32_t i = 0; i < tmpData.Length(); ++i) {
        bool match = false;
        if (dedupe) {
            MOZ_ASSERT(overwrite);
            // If this is an old profile, then we might need to deduplicate.  In
            // theory this can be removed in the future (Bug 1248449)
            for (uint32_t j = 0; j < mData.Length(); ++j) {
                // Use same comparison as RegisterServiceWorker. Scope contains
                // basic origin information.  Combine with any principal attributes.
                if (Equivalent(tmpData[i], mData[j])) {
                    // Last match wins, just like legacy loading used to do in
                    // the ServiceWorkerManager.
                    mData[j] = tmpData[i];
                    // Dupe found, so overwrite file with reduced list.
                    match = true;
                    break;
                }
            }
        } else {
#ifdef DEBUG
            // Otherwise assert no duplications in debug builds.
            for (uint32_t j = 0; j < mData.Length(); ++j) {
                MOZ_ASSERT(!Equivalent(tmpData[i], mData[j]));
            }
#endif
        }
        if (!match) {
            mData.AppendElement(tmpData[i]);
        }
    }

    // Overwrite previous version.
    // Cannot call SaveData directly because gtest uses main-thread.
    if (overwrite && NS_FAILED(WriteData())) {
        NS_WARNING("Failed to write data for the ServiceWorker Registations.");
        DeleteData();
    }

    return NS_OK;
}