Bug 1406358 - Keep SpeechDelegate object until speaking is finished. r?eeejay
The delegate property of NSSpeechSynthesizer doesn't seem to add refcount from 10.13, so we have to keep SpeechDelegate object until speech is finished.
MozReview-Commit-ID: EVtMOPytkjR
--- a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
@@ -12,71 +12,122 @@
#include "mozilla/dom/nsSynthVoiceRegistry.h"
#include "mozilla/dom/nsSpeechTask.h"
#include "mozilla/Preferences.h"
#include "mozilla/Assertions.h"
#include "OSXSpeechSynthesizerService.h"
#import <Cocoa/Cocoa.h>
+@class SpeechDelegate;
+
// We can escape the default delimiters ("[[" and "]]") by temporarily
// changing the delimiters just before they appear, and changing them back
// just after.
#define DLIM_ESCAPE_START "[[dlim (( ))]]"
#define DLIM_ESCAPE_END "((dlim [[ ]]))"
using namespace mozilla;
class SpeechTaskCallback final : public nsISpeechTaskCallback
{
public:
SpeechTaskCallback(nsISpeechTask* aTask,
NSSpeechSynthesizer* aSynth,
- const nsTArray<size_t>& aOffsets)
- : mTask(aTask)
- , mSpeechSynthesizer(aSynth)
- , mOffsets(aOffsets)
- {
- mStartingTime = TimeStamp::Now();
- }
+ const nsTArray<size_t>& aOffsets);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SpeechTaskCallback, nsISpeechTaskCallback)
NS_DECL_NSISPEECHTASKCALLBACK
void OnWillSpeakWord(uint32_t aIndex, uint32_t aLength);
void OnError(uint32_t aIndex);
void OnDidFinishSpeaking();
private:
- virtual ~SpeechTaskCallback()
- {
- [mSpeechSynthesizer release];
- }
+ virtual ~SpeechTaskCallback();
float GetTimeDurationFromStart();
nsCOMPtr<nsISpeechTask> mTask;
NSSpeechSynthesizer* mSpeechSynthesizer;
+ SpeechDelegate* mDelegate;
TimeStamp mStartingTime;
uint32_t mCurrentIndex;
nsTArray<size_t> mOffsets;
};
+@interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate>
+{
+@private
+ SpeechTaskCallback* mCallback;
+}
+
+ - (id)initWithCallback:(SpeechTaskCallback*)aCallback;
+@end
+
+@implementation SpeechDelegate
+- (id)initWithCallback:(SpeechTaskCallback*)aCallback
+{
+ [super init];
+ mCallback = aCallback;
+ return self;
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
+ willSpeakWord:(NSRange)aRange ofString:(NSString*)aString
+{
+ mCallback->OnWillSpeakWord(aRange.location, aRange.length);
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
+ didFinishSpeaking:(BOOL)aFinishedSpeaking
+{
+ mCallback->OnDidFinishSpeaking();
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer*)aSender
+ didEncounterErrorAtIndex:(NSUInteger)aCharacterIndex
+ ofString:(NSString*)aString
+ message:(NSString*)aMessage
+{
+ mCallback->OnError(aCharacterIndex);
+}
+@end
+
NS_IMPL_CYCLE_COLLECTION(SpeechTaskCallback, mTask);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechTaskCallback)
NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechTaskCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechTaskCallback)
+SpeechTaskCallback::SpeechTaskCallback(nsISpeechTask* aTask,
+ NSSpeechSynthesizer* aSynth,
+ const nsTArray<size_t>& aOffsets)
+ : mTask(aTask)
+ , mSpeechSynthesizer(aSynth)
+ , mOffsets(aOffsets)
+{
+ mDelegate = [[SpeechDelegate alloc] initWithCallback:this];
+ [mSpeechSynthesizer setDelegate:mDelegate];
+ mStartingTime = TimeStamp::Now();
+}
+
+SpeechTaskCallback::~SpeechTaskCallback()
+{
+ [mSpeechSynthesizer setDelegate:nil];
+ [mDelegate release];
+ [mSpeechSynthesizer release];
+}
+
NS_IMETHODIMP
SpeechTaskCallback::OnCancel()
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
[mSpeechSynthesizer stopSpeaking];
return NS_OK;
@@ -161,54 +212,16 @@ void
SpeechTaskCallback::OnDidFinishSpeaking()
{
mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
// no longer needed
[mSpeechSynthesizer setDelegate:nil];
mTask = nullptr;
}
-@interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate>
-{
-@private
- SpeechTaskCallback* mCallback;
-}
-
- - (id)initWithCallback:(SpeechTaskCallback*)aCallback;
-@end
-
-@implementation SpeechDelegate
-- (id)initWithCallback:(SpeechTaskCallback*)aCallback
-{
- [super init];
- mCallback = aCallback;
- return self;
-}
-
-- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
- willSpeakWord:(NSRange)aRange ofString:(NSString*)aString
-{
- mCallback->OnWillSpeakWord(aRange.location, aRange.length);
-}
-
-- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
- didFinishSpeaking:(BOOL)aFinishedSpeaking
-{
- mCallback->OnDidFinishSpeaking();
-}
-
-- (void)speechSynthesizer:(NSSpeechSynthesizer*)aSender
- didEncounterErrorAtIndex:(NSUInteger)aCharacterIndex
- ofString:(NSString*)aString
- message:(NSString*)aMessage
-{
- mCallback->OnError(aCharacterIndex);
-}
-@end
-
namespace mozilla {
namespace dom {
struct OSXVoice
{
OSXVoice() : mIsDefault(false)
{
}
@@ -424,20 +437,16 @@ OSXSpeechSynthesizerService::Speak(const
offsets.AppendElement(i);
}
}
RefPtr<SpeechTaskCallback> callback = new SpeechTaskCallback(aTask, synth, offsets);
nsresult rv = aTask->Setup(callback, 0, 0, 0);
NS_ENSURE_SUCCESS(rv, rv);
- SpeechDelegate* delegate = [[SpeechDelegate alloc] initWithCallback:callback];
- [synth setDelegate:delegate];
- [delegate release ];
-
NSString* text = nsCocoaUtils::ToNSString(escapedText);
BOOL success = [synth startSpeakingString:text];
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
aTask->DispatchStart();
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;