| /* |
| * Copyright (C) 2007, 2008, 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "modules/webdatabase/SQLTransaction.h" |
| |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/html/VoidCallback.h" |
| #include "core/inspector/InspectorInstrumentation.h" |
| #include "modules/webdatabase/Database.h" |
| #include "modules/webdatabase/DatabaseAuthorizer.h" |
| #include "modules/webdatabase/DatabaseContext.h" |
| #include "modules/webdatabase/DatabaseThread.h" |
| #include "modules/webdatabase/SQLError.h" |
| #include "modules/webdatabase/SQLStatementCallback.h" |
| #include "modules/webdatabase/SQLStatementErrorCallback.h" |
| #include "modules/webdatabase/SQLTransactionBackend.h" |
| #include "modules/webdatabase/SQLTransactionCallback.h" |
| #include "modules/webdatabase/SQLTransactionClient.h" // FIXME: Should be used in the backend only. |
| #include "modules/webdatabase/SQLTransactionErrorCallback.h" |
| #include "modules/webdatabase/StorageLog.h" |
| #include "wtf/StdLibExtras.h" |
| #include "wtf/Vector.h" |
| |
| namespace blink { |
| |
| SQLTransaction* SQLTransaction::create( |
| Database* db, |
| SQLTransactionCallback* callback, |
| VoidCallback* successCallback, |
| SQLTransactionErrorCallback* errorCallback, |
| bool readOnly) { |
| return new SQLTransaction(db, callback, successCallback, errorCallback, |
| readOnly); |
| } |
| |
| SQLTransaction::SQLTransaction(Database* db, |
| SQLTransactionCallback* callback, |
| VoidCallback* successCallback, |
| SQLTransactionErrorCallback* errorCallback, |
| bool readOnly) |
| : m_database(db), |
| m_callback(callback), |
| m_successCallback(successCallback), |
| m_errorCallback(errorCallback), |
| m_executeSqlAllowed(false), |
| m_readOnly(readOnly) { |
| DCHECK(isMainThread()); |
| ASSERT(m_database); |
| InspectorInstrumentation::asyncTaskScheduled(db->getExecutionContext(), |
| "SQLTransaction", this, true); |
| } |
| |
| SQLTransaction::~SQLTransaction() {} |
| |
| DEFINE_TRACE(SQLTransaction) { |
| visitor->trace(m_database); |
| visitor->trace(m_backend); |
| visitor->trace(m_callback); |
| visitor->trace(m_successCallback); |
| visitor->trace(m_errorCallback); |
| } |
| |
| bool SQLTransaction::hasCallback() const { |
| return m_callback; |
| } |
| |
| bool SQLTransaction::hasSuccessCallback() const { |
| return m_successCallback; |
| } |
| |
| bool SQLTransaction::hasErrorCallback() const { |
| return m_errorCallback; |
| } |
| |
| void SQLTransaction::setBackend(SQLTransactionBackend* backend) { |
| ASSERT(!m_backend); |
| m_backend = backend; |
| } |
| |
| SQLTransaction::StateFunction SQLTransaction::stateFunctionFor( |
| SQLTransactionState state) { |
| static const StateFunction stateFunctions[] = { |
| &SQLTransaction::unreachableState, // 0. illegal |
| &SQLTransaction::unreachableState, // 1. idle |
| &SQLTransaction::unreachableState, // 2. acquireLock |
| &SQLTransaction::unreachableState, // 3. openTransactionAndPreflight |
| &SQLTransaction::sendToBackendState, // 4. runStatements |
| &SQLTransaction::unreachableState, // 5. postflightAndCommit |
| &SQLTransaction::sendToBackendState, // 6. cleanupAndTerminate |
| &SQLTransaction:: |
| sendToBackendState, // 7. cleanupAfterTransactionErrorCallback |
| &SQLTransaction::deliverTransactionCallback, // 8. |
| &SQLTransaction::deliverTransactionErrorCallback, // 9. |
| &SQLTransaction::deliverStatementCallback, // 10. |
| &SQLTransaction::deliverQuotaIncreaseCallback, // 11. |
| &SQLTransaction::deliverSuccessCallback // 12. |
| }; |
| |
| ASSERT(WTF_ARRAY_LENGTH(stateFunctions) == |
| static_cast<int>(SQLTransactionState::NumberOfStates)); |
| ASSERT(state < SQLTransactionState::NumberOfStates); |
| |
| return stateFunctions[static_cast<int>(state)]; |
| } |
| |
| // requestTransitToState() can be called from the backend. Hence, it should |
| // NOT be modifying SQLTransactionBackend in general. The only safe field to |
| // modify is m_requestedState which is meant for this purpose. |
| void SQLTransaction::requestTransitToState(SQLTransactionState nextState) { |
| #if DCHECK_IS_ON() |
| STORAGE_DVLOG(1) << "Scheduling " << nameForSQLTransactionState(nextState) |
| << " for transaction " << this; |
| #endif |
| m_requestedState = nextState; |
| m_database->scheduleTransactionCallback(this); |
| } |
| |
| SQLTransactionState SQLTransaction::nextStateForTransactionError() { |
| ASSERT(m_transactionError); |
| if (hasErrorCallback()) |
| return SQLTransactionState::DeliverTransactionErrorCallback; |
| |
| // No error callback, so fast-forward to: |
| // Transaction Step 11 - Rollback the transaction. |
| return SQLTransactionState::CleanupAfterTransactionErrorCallback; |
| } |
| |
| SQLTransactionState SQLTransaction::deliverTransactionCallback() { |
| bool shouldDeliverErrorCallback = false; |
| InspectorInstrumentation::AsyncTask asyncTask( |
| m_database->getExecutionContext(), this); |
| |
| // Spec 4.3.2 4: Invoke the transaction callback with the new SQLTransaction |
| // object. |
| if (SQLTransactionCallback* callback = m_callback.release()) { |
| m_executeSqlAllowed = true; |
| shouldDeliverErrorCallback = !callback->handleEvent(this); |
| m_executeSqlAllowed = false; |
| } |
| |
| // Spec 4.3.2 5: If the transaction callback was null or raised an exception, |
| // jump to the error callback. |
| SQLTransactionState nextState = SQLTransactionState::RunStatements; |
| if (shouldDeliverErrorCallback) { |
| m_database->reportStartTransactionResult(5, SQLError::kUnknownErr, 0); |
| m_transactionError = SQLErrorData::create( |
| SQLError::kUnknownErr, |
| "the SQLTransactionCallback was null or threw an exception"); |
| nextState = SQLTransactionState::DeliverTransactionErrorCallback; |
| } |
| m_database->reportStartTransactionResult(0, -1, 0); // OK |
| return nextState; |
| } |
| |
| SQLTransactionState SQLTransaction::deliverTransactionErrorCallback() { |
| InspectorInstrumentation::AsyncTask asyncTask( |
| m_database->getExecutionContext(), this); |
| InspectorInstrumentation::asyncTaskCanceled(m_database->getExecutionContext(), |
| this); |
| |
| // Spec 4.3.2.10: If exists, invoke error callback with the last |
| // error to have occurred in this transaction. |
| if (SQLTransactionErrorCallback* errorCallback = m_errorCallback.release()) { |
| // If we get here with an empty m_transactionError, then the backend |
| // must be waiting in the idle state waiting for this state to finish. |
| // Hence, it's thread safe to fetch the backend transactionError without |
| // a lock. |
| if (!m_transactionError) { |
| ASSERT(m_backend->transactionError()); |
| m_transactionError = SQLErrorData::create(*m_backend->transactionError()); |
| } |
| ASSERT(m_transactionError); |
| errorCallback->handleEvent(SQLError::create(*m_transactionError)); |
| |
| m_transactionError = nullptr; |
| } |
| |
| clearCallbacks(); |
| |
| // Spec 4.3.2.10: Rollback the transaction. |
| return SQLTransactionState::CleanupAfterTransactionErrorCallback; |
| } |
| |
| SQLTransactionState SQLTransaction::deliverStatementCallback() { |
| DCHECK(isMainThread()); |
| // Spec 4.3.2.6.6 and 4.3.2.6.3: If the statement callback went wrong, jump to |
| // the transaction error callback. Otherwise, continue to loop through the |
| // statement queue. |
| m_executeSqlAllowed = true; |
| |
| SQLStatement* currentStatement = m_backend->currentStatement(); |
| ASSERT(currentStatement); |
| |
| bool result = currentStatement->performCallback(this); |
| |
| m_executeSqlAllowed = false; |
| |
| if (result) { |
| m_database->reportCommitTransactionResult(2, SQLError::kUnknownErr, 0); |
| m_transactionError = |
| SQLErrorData::create(SQLError::kUnknownErr, |
| "the statement callback raised an exception or " |
| "statement error callback did not return false"); |
| return nextStateForTransactionError(); |
| } |
| return SQLTransactionState::RunStatements; |
| } |
| |
| SQLTransactionState SQLTransaction::deliverQuotaIncreaseCallback() { |
| DCHECK(isMainThread()); |
| ASSERT(m_backend->currentStatement()); |
| |
| bool shouldRetryCurrentStatement = |
| m_database->transactionClient()->didExceedQuota(database()); |
| m_backend->setShouldRetryCurrentStatement(shouldRetryCurrentStatement); |
| |
| return SQLTransactionState::RunStatements; |
| } |
| |
| SQLTransactionState SQLTransaction::deliverSuccessCallback() { |
| DCHECK(isMainThread()); |
| InspectorInstrumentation::AsyncTask asyncTask( |
| m_database->getExecutionContext(), this); |
| InspectorInstrumentation::asyncTaskCanceled(m_database->getExecutionContext(), |
| this); |
| |
| // Spec 4.3.2.8: Deliver success callback. |
| if (VoidCallback* successCallback = m_successCallback.release()) |
| successCallback->handleEvent(); |
| |
| clearCallbacks(); |
| |
| // Schedule a "post-success callback" step to return control to the database |
| // thread in case there are further transactions queued up for this Database. |
| return SQLTransactionState::CleanupAndTerminate; |
| } |
| |
| // This state function is used as a stub function to plug unimplemented states |
| // in the state dispatch table. They are unimplemented because they should |
| // never be reached in the course of correct execution. |
| SQLTransactionState SQLTransaction::unreachableState() { |
| ASSERT_NOT_REACHED(); |
| return SQLTransactionState::End; |
| } |
| |
| SQLTransactionState SQLTransaction::sendToBackendState() { |
| ASSERT(m_nextState != SQLTransactionState::Idle); |
| m_backend->requestTransitToState(m_nextState); |
| return SQLTransactionState::Idle; |
| } |
| |
| void SQLTransaction::performPendingCallback() { |
| DCHECK(isMainThread()); |
| computeNextStateAndCleanupIfNeeded(); |
| runStateMachine(); |
| } |
| |
| void SQLTransaction::executeSQL(const String& sqlStatement, |
| const Vector<SQLValue>& arguments, |
| SQLStatementCallback* callback, |
| SQLStatementErrorCallback* callbackError, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| if (!m_executeSqlAllowed) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "SQL execution is disallowed."); |
| return; |
| } |
| |
| if (!m_database->opened()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "The database has not been opened."); |
| return; |
| } |
| |
| int permissions = DatabaseAuthorizer::ReadWriteMask; |
| if (!m_database->getDatabaseContext()->allowDatabaseAccess()) |
| permissions |= DatabaseAuthorizer::NoAccessMask; |
| else if (m_readOnly) |
| permissions |= DatabaseAuthorizer::ReadOnlyMask; |
| |
| SQLStatement* statement = |
| SQLStatement::create(m_database.get(), callback, callbackError); |
| m_backend->executeSQL(statement, sqlStatement, arguments, permissions); |
| } |
| |
| void SQLTransaction::executeSql(ScriptState* scriptState, |
| const String& sqlStatement, |
| ExceptionState& exceptionState) { |
| executeSQL(sqlStatement, Vector<SQLValue>(), nullptr, nullptr, |
| exceptionState); |
| } |
| |
| void SQLTransaction::executeSql(ScriptState* scriptState, |
| const String& sqlStatement, |
| const Nullable<Vector<ScriptValue>>& arguments, |
| SQLStatementCallback* callback, |
| SQLStatementErrorCallback* callbackError, |
| ExceptionState& exceptionState) { |
| Vector<SQLValue> sqlValues; |
| if (!arguments.isNull()) |
| sqlValues = toImplArray<Vector<SQLValue>>( |
| arguments.get(), scriptState->isolate(), exceptionState); |
| executeSQL(sqlStatement, sqlValues, callback, callbackError, exceptionState); |
| } |
| |
| bool SQLTransaction::computeNextStateAndCleanupIfNeeded() { |
| // Only honor the requested state transition if we're not supposed to be |
| // cleaning up and shutting down: |
| if (m_database->opened()) { |
| setStateToRequestedState(); |
| ASSERT(m_nextState == SQLTransactionState::End || |
| m_nextState == SQLTransactionState::DeliverTransactionCallback || |
| m_nextState == |
| SQLTransactionState::DeliverTransactionErrorCallback || |
| m_nextState == SQLTransactionState::DeliverStatementCallback || |
| m_nextState == SQLTransactionState::DeliverQuotaIncreaseCallback || |
| m_nextState == SQLTransactionState::DeliverSuccessCallback); |
| #if DCHECK_IS_ON() |
| STORAGE_DVLOG(1) << "Callback " << nameForSQLTransactionState(m_nextState); |
| #endif |
| return false; |
| } |
| |
| clearCallbacks(); |
| m_nextState = SQLTransactionState::CleanupAndTerminate; |
| |
| return true; |
| } |
| |
| void SQLTransaction::clearCallbacks() { |
| m_callback.clear(); |
| m_successCallback.clear(); |
| m_errorCallback.clear(); |
| } |
| |
| SQLTransactionErrorCallback* SQLTransaction::releaseErrorCallback() { |
| return m_errorCallback.release(); |
| } |
| |
| } // namespace blink |