PassRefPtr<StorageMap> StorageMap::setItem(const String& key, const String& value, String& oldValue, bool& quotaException) { ASSERT(!value.isNull()); quotaException = false; // Implement copy-on-write semantics here. We're guaranteed that the only refs of StorageMaps belong to Storage objects // so if more than one Storage object refs this map, copy it before mutating it. if (refCount() > 1) { RefPtr<StorageMap> newStorageMap = copy(); newStorageMap->setItem(key, value, oldValue, quotaException); return newStorageMap.release(); } // Quota tracking. This is done in a couple of steps to keep the overflow tracking simple. unsigned newLength = m_currentLength; bool overflow = newLength + value.length() < newLength; newLength += value.length(); oldValue = m_map.get(key); overflow |= newLength - oldValue.length() > newLength; newLength -= oldValue.length(); unsigned adjustedKeyLength = oldValue.isNull() ? key.length() : 0; overflow |= newLength + adjustedKeyLength < newLength; newLength += adjustedKeyLength; ASSERT(!overflow); // Overflow is bad...even if quotas are off. bool overQuota = newLength > m_quotaSize / sizeof(UChar); if (m_quotaSize != noQuota && (overflow || overQuota)) { quotaException = true; return 0; } m_currentLength = newLength; HashMap<String, String>::AddResult addResult = m_map.add(key, value); if (!addResult.isNewEntry) addResult.iterator->value = value; invalidateIterator(); return 0; }
PassRefPtr<StorageMap> StorageMap::removeItem(const String& key, String& oldValue) { // Implement copy-on-write semantics here. We're guaranteed that the only refs of StorageMaps belong to Storage objects // so if more than one Storage object refs this map, copy it before mutating it. if (refCount() > 1) { RefPtr<StorageMap> newStorage = copy(); newStorage->removeItem(key, oldValue); return newStorage.release(); } oldValue = m_map.take(key); if (!oldValue.isNull()) { invalidateIterator(); ASSERT(m_currentLength - key.length() <= m_currentLength); m_currentLength -= key.length(); } ASSERT(m_currentLength - oldValue.length() <= m_currentLength); m_currentLength -= oldValue.length(); return 0; }
bool ContainerNode::replaceChild(PassRefPtr<Node> newChild, Node* oldChild, ExceptionCode& ec, AttachBehavior attachBehavior) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parentOrShadowHostNode()); RefPtr<Node> protect(this); ec = 0; if (oldChild == newChild) // nothing to do return true; if (!oldChild) { ec = NOT_FOUND_ERR; return false; } // Make sure replacing the old child with the new is ok if (!checkReplaceChild(this, newChild.get(), oldChild, ec)) return false; // NOT_FOUND_ERR: Raised if oldChild is not a child of this node. if (oldChild->parentNode() != this) { ec = NOT_FOUND_ERR; return false; } ChildListMutationScope mutation(this); RefPtr<Node> next = oldChild->nextSibling(); // Remove the node we're replacing RefPtr<Node> removedChild = oldChild; removeChild(oldChild, ec); if (ec) return false; if (next && (next->previousSibling() == newChild || next == newChild)) // nothing to do return true; // Does this one more time because removeChild() fires a MutationEvent. if (!checkReplaceChild(this, newChild.get(), oldChild, ec)) return false; NodeVector targets; collectChildrenAndRemoveFromOldParent(newChild.get(), targets, ec); if (ec) return false; // Does this yet another check because collectChildrenAndRemoveFromOldParent() fires a MutationEvent. if (!checkReplaceChild(this, newChild.get(), oldChild, ec)) return false; InspectorInstrumentation::willInsertDOMNode(document(), this); // Add the new child(ren) for (NodeVector::const_iterator it = targets.begin(); it != targets.end(); ++it) { Node* child = it->get(); // Due to arbitrary code running in response to a DOM mutation event it's // possible that "next" is no longer a child of "this". // It's also possible that "child" has been inserted elsewhere. // In either of those cases, we'll just stop. if (next && next->parentNode() != this) break; if (child->parentNode()) break; treeScope()->adoptIfNeeded(child); // Add child before "next". { NoEventDispatchAssertion assertNoEventDispatch; if (next) insertBeforeCommon(next.get(), child); else appendChildToContainer(child, this); } updateTreeAfterInsertion(this, child, attachBehavior); } dispatchSubtreeModifiedEvent(); return true; }
bool ContainerNode::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec, AttachBehavior attachBehavior) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parentOrShadowHostNode()); RefPtr<Node> protect(this); ec = 0; // insertBefore(node, 0) is equivalent to appendChild(node) if (!refChild) return appendChild(newChild, ec, attachBehavior); // Make sure adding the new child is OK. if (!checkAddChild(this, newChild.get(), ec)) return false; // NOT_FOUND_ERR: Raised if refChild is not a child of this node if (refChild->parentNode() != this) { ec = NOT_FOUND_ERR; return false; } if (refChild->previousSibling() == newChild || refChild == newChild) // nothing to do return true; RefPtr<Node> next = refChild; NodeVector targets; collectChildrenAndRemoveFromOldParent(newChild.get(), targets, ec); if (ec) return false; if (targets.isEmpty()) return true; // We need this extra check because collectChildrenAndRemoveFromOldParent() can fire mutation events. if (!checkAcceptChildGuaranteedNodeTypes(this, newChild.get(), ec)) return false; InspectorInstrumentation::willInsertDOMNode(document(), this); ChildListMutationScope mutation(this); for (NodeVector::const_iterator it = targets.begin(); it != targets.end(); ++it) { Node* child = it->get(); // Due to arbitrary code running in response to a DOM mutation event it's // possible that "next" is no longer a child of "this". // It's also possible that "child" has been inserted elsewhere. // In either of those cases, we'll just stop. if (next->parentNode() != this) break; if (child->parentNode()) break; treeScope()->adoptIfNeeded(child); insertBeforeCommon(next.get(), child); updateTreeAfterInsertion(this, child, attachBehavior); } dispatchSubtreeModifiedEvent(); return true; }
void DatabaseThread::databaseThread() { { // Wait for DatabaseThread::start() to complete. MutexLocker lock(m_threadCreationMutex); LOG(StorageAPI, "Started DatabaseThread %p", this); } AutodrainedPool pool; while (OwnPtr<DatabaseTask> task = m_queue.waitForMessage()) { task->performTask(); pool.cycle(); } // Clean up the list of all pending transactions on this database thread m_transactionCoordinator->shutdown(); LOG(StorageAPI, "About to detach thread %i and clear the ref to DatabaseThread %p, which currently has %i ref(s)", m_threadID, this, refCount()); // Close the databases that we ran transactions on. This ensures that if any transactions are still open, they are rolled back and we don't leave the database in an // inconsistent or locked state. if (m_openDatabaseSet.size() > 0) { // As the call to close will modify the original set, we must take a copy to iterate over. DatabaseSet openSetCopy; openSetCopy.swap(m_openDatabaseSet); DatabaseSet::iterator end = openSetCopy.end(); for (DatabaseSet::iterator it = openSetCopy.begin(); it != end; ++it) (*it).get()->close(); } // Detach the thread so its resources are no longer of any concern to anyone else detachThread(m_threadID); DatabaseTaskSynchronizer* cleanupSync = m_cleanupSync; // Clear the self refptr, possibly resulting in deletion m_selfRef = 0; if (cleanupSync) // Someone wanted to know when we were done cleaning up. cleanupSync->taskCompleted(); }
void FileThread::runLoop() { { // Wait for FileThread::start() to complete to have m_threadID // established before starting the main loop. MutexLocker lock(m_threadCreationMutex); LOG(FileAPI, "Started FileThread %p", this); } while (OwnPtr<Task> task = m_queue.waitForMessage()) { AutodrainedPool pool; task->performTask(); } LOG(FileAPI, "About to detach thread %i and clear the ref to FileThread %p, which currently has %i ref(s)", m_threadID, this, refCount()); detachThread(m_threadID); // Clear the self refptr, possibly resulting in deletion m_selfRef = 0; }
TEST(WTF_WorkQueue, TwoQueues) { Lock m_lock; Condition m_testQueue1Completed, m_testQueue2Completed; Vector<std::string> m_functionCallOrder; bool calledSimpleTest = false; bool calledLongTest = false; bool calledThirdTest = false; auto queue1 = WorkQueue::create("com.apple.WebKit.Test.twoQueues1"); auto queue2 = WorkQueue::create("com.apple.WebKit.Test.twoQueues2"); EXPECT_EQ(1, queue1->refCount()); EXPECT_EQ(1, queue2->refCount()); LockHolder locker(m_lock); queue1->dispatch([&](void) { m_functionCallOrder.append(simpleTestLabel); calledSimpleTest = true; }); queue2->dispatch([&](void) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); LockHolder locker(m_lock); // Will fail if queue2 took the mutex before queue1. EXPECT_TRUE(calledThirdTest); m_functionCallOrder.append(longTestLabel); calledLongTest = true; m_testQueue2Completed.notifyOne(); }); queue1->dispatch([&](void) { LockHolder locker(m_lock); m_functionCallOrder.append(thirdTestLabel); calledThirdTest = true; m_testQueue1Completed.notifyOne(); }); m_testQueue1Completed.wait(m_lock); EXPECT_TRUE(calledSimpleTest); EXPECT_FALSE(calledLongTest); EXPECT_TRUE(calledThirdTest); m_testQueue2Completed.wait(m_lock); EXPECT_TRUE(calledSimpleTest); EXPECT_TRUE(calledLongTest); EXPECT_TRUE(calledThirdTest); EXPECT_EQ(static_cast<size_t>(3), m_functionCallOrder.size()); EXPECT_STREQ(simpleTestLabel, m_functionCallOrder[0].c_str()); EXPECT_STREQ(thirdTestLabel, m_functionCallOrder[1].c_str()); EXPECT_STREQ(longTestLabel, m_functionCallOrder[2].c_str()); }
void* DatabaseThread::databaseThread() { LOG(StorageAPI, "Starting DatabaseThread %p", this); AutodrainedPool pool; while (true) { RefPtr<DatabaseTask> task; if (!m_queue.waitForMessage(task)) break; task->performTask(); pool.cycle(); } LOG(StorageAPI, "About to detach thread %i and clear the ref to DatabaseThread %p, which currently has %i ref(s)", m_threadID, this, refCount()); // Detach the thread so its resources are no longer of any concern to anyone else detachThread(m_threadID); // Clear the self refptr, possibly resulting in deletion m_selfRef = 0; return 0; }
bool ContainerNode::removeChild(Node* oldChild, ExceptionCode& ec) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parentOrShadowHostNode()); Ref<ContainerNode> protect(*this); ec = 0; // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. if (isReadOnlyNode()) { ec = NO_MODIFICATION_ALLOWED_ERR; return false; } // NOT_FOUND_ERR: Raised if oldChild is not a child of this node. if (!oldChild || oldChild->parentNode() != this) { ec = NOT_FOUND_ERR; return false; } Ref<Node> child(*oldChild); document().removeFocusedNodeOfSubtree(&child.get()); #if ENABLE(FULLSCREEN_API) document().removeFullScreenElementOfSubtree(&child.get()); #endif // Events fired when blurring currently focused node might have moved this // child into a different parent. if (child->parentNode() != this) { ec = NOT_FOUND_ERR; return false; } willRemoveChild(child.get()); // Mutation events might have moved this child into a different parent. if (child->parentNode() != this) { ec = NOT_FOUND_ERR; return false; } { WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; Node* prev = child->previousSibling(); Node* next = child->nextSibling(); removeBetween(prev, next, child.get()); notifyChildRemoved(child.get(), prev, next, ChildChangeSourceAPI); ChildNodeRemovalNotifier(*this).notify(child.get()); } if (document().svgExtensions()) { Element* shadowHost = this->shadowHost(); if (!shadowHost || !shadowHost->hasTagName(SVGNames::useTag)) document().accessSVGExtensions()->rebuildElements(); } dispatchSubtreeModifiedEvent(); return true; }
void* DatabaseThread::databaseThread() { { // Wait for DatabaseThread::start() to complete. MutexLocker lock(m_threadCreationMutex); LOG(StorageAPI, "Started DatabaseThread %p", this); } AutodrainedPool pool; while (true) { RefPtr<DatabaseTask> task; if (!m_queue.waitForMessage(task)) break; task->performTask(); pool.cycle(); } LOG(StorageAPI, "About to detach thread %i and clear the ref to DatabaseThread %p, which currently has %i ref(s)", m_threadID, this, refCount()); // Close the databases that we ran transactions on. This ensures that if any transactions are still open, they are rolled back and we don't leave the database in an // inconsistent or locked state. if (m_openDatabaseSet.size() > 0) { // As the call to close will modify the original set, we must take a copy to iterate over. DatabaseSet openSetCopy; openSetCopy.swap(m_openDatabaseSet); DatabaseSet::iterator end = openSetCopy.end(); for (DatabaseSet::iterator it = openSetCopy.begin(); it != end; ++it) (*it)->close(); } // Detach the thread so its resources are no longer of any concern to anyone else detachThread(m_threadID); // Clear the self refptr, possibly resulting in deletion m_selfRef = 0; return 0; }