Skip to content

Commit

Permalink
Change how talking tabs are kept track of
Browse files Browse the repository at this point in the history
  • Loading branch information
joelpurra committed Mar 2, 2017
1 parent 20a830a commit f61fe58
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 60 deletions.
205 changes: 145 additions & 60 deletions src/background.js
Expand Up @@ -28,8 +28,9 @@ detectPageLanguage:false,
executeLogToPage:false,
executePlugOnce:false,
executeScriptInAllFrames:false,
executeScriptInTopFrame:false,
flatten:false,
getCurrentActiveTab:false,
getCurrentActiveTabId:false,
getMappedVoices:false,
getVoices:false,
isCurrentPageInternalToTalkie:false,
Expand All @@ -50,7 +51,7 @@ window:false,
// https://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html#tts-section
// https://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html#examples-synthesis
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API#Speech_synthesis
log("Start", "Loading code");
log("Start", "Loading backgrund code");

const MAX_UTTERANCE_TEXT_LENGTH = 100;

Expand Down Expand Up @@ -122,34 +123,6 @@ const setup = () => promiseTry(

return synthesizer;
});
})
.then((synthesizer) => {
const unload = () => {
log("Start", "Unloading");

return executeGetTalkieIsSpeaking()
.then((talkieIsSpeaking) => {
// Clear all text if Talkie was speaking.
if (talkieIsSpeaking) {
// TODO: use stopSpeaking()?
synthesizer.cancel();
}

return undefined;
})
.then(() => {
// Reset the system to resume playback, just to be nice to the world.
synthesizer.resume();

log("Done", "Unloading");

return undefined;
});
};

chrome.runtime.onSuspend.addListener(unload);

return synthesizer;
}
);

Expand Down Expand Up @@ -393,20 +366,83 @@ const onlyLastCallerContinueSpeakingProvider = () => {
);
};

const executeGetTalkieIsSpeakingCode = "window.talkieIsSpeaking;";
const executeGetTalkieIsSpeaking = () => executeScriptInTopFrame(executeGetTalkieIsSpeakingCode)
.then((talkieIsSpeaking) => {
return talkieIsSpeaking === "true";
let currentSpeakingTab = null;

const getSpeakingTabId = () => promiseTry(
() => currentSpeakingTab
);

const setSpeakingTabId = (tabId) => isSpeakingTabId(tabId)
.then((isTabSpeaking) => {
if (isTabSpeaking) {
throw new Error(`Tried to set tab ${tabId} as speaking, but another tab was already speaking.`);
}

currentSpeakingTab = tabId;

return undefined;
});

const setTabIsDoneSpeaking = (tabId) => isSpeakingTabId(tabId)
.then((isTabSpeaking) => {
// TODO: throw if it's not the same tabId as the currently speaking tab?
if (isTabSpeaking) {
currentSpeakingTab = null;
}

return undefined;
});

const executeSetTalkieIsSpeakingCode = "window.talkieIsSpeaking = true;";
const executeSetTalkieIsSpeaking = () => executeScriptInTopFrame(executeSetTalkieIsSpeakingCode);
const isSpeakingTabId = (tabId) => promiseTry(
() => currentSpeakingTab !== null && tabId === currentSpeakingTab
);

const isSpeaking = () => getSpeakingTabId()
// TODO: check synthesizer.speaking === true?
.then((speakingTabId) => speakingTabId !== null);

const setActiveTabIsDoneSpeaking = () => getCurrentActiveTabId()
.then((activeTabId) => setTabIsDoneSpeaking(activeTabId));

const setActiveTabAsSpeaking = () => getCurrentActiveTab()
.then((activeTab) => {
// NOTE: some tabs can't be retreived.
if (!activeTab) {
return undefined;
}

const activeTabId = activeTab.id;

const executeSetTalkieIsNotSpeakingCode = "window.talkieIsSpeaking = null;";
const executeSetTalkieIsNotSpeaking = () => executeScriptInTopFrame(executeSetTalkieIsNotSpeakingCode);
return setSpeakingTabId(activeTabId);
});

const isActiveTabSpeaking = () => getCurrentActiveTabId()
.then((activeTabId) => isSpeakingTabId(activeTabId));

let preventSuspensionPort = null;

const executeAddOnBeforeUnloadHandlersCode = "window.talkieIsSpeaking === undefined && window.addEventListener(\"beforeunload\", function () { window.talkieIsSpeaking && window.speechSynthesis.cancel(); });";
const executeAddOnBeforeUnloadHandlers = () => executeScriptInTopFrame(executeAddOnBeforeUnloadHandlersCode);
const preventSuspend = () => promiseTry(
() => {
const connectOptions = {
name: "Talkie prevents suspension!",
};

preventSuspensionPort = chrome.runtime.connect(connectOptions);
}
);

const allowSuspend = () => promiseTry(
() => {
if (!preventSuspensionPort) {
throw new Error("No suspension prevention port set.");
}

// https://developer.chrome.com/extensions/runtime#type-Port
preventSuspensionPort.disconnect();

preventSuspensionPort = null;
}
);

const executeGetFramesSelectionTextAndLanguageCode = "function talkieGetParentElementLanguages(element) { return [].concat(element && element.getAttribute(\"lang\")).concat(element.parentElement && talkieGetParentElementLanguages(element.parentElement)); }; var talkieSelectionData = { text: document.getSelection().toString(), htmlTagLanguage: document.getElementsByTagName(\"html\")[0].getAttribute(\"lang\"), parentElementsLanguages: talkieGetParentElementLanguages(document.getSelection().rangeCount > 0 && document.getSelection().getRangeAt(0).startContainer.parentElement) }; talkieSelectionData";
const executeGetFramesSelectionTextAndLanguage = () => executeScriptInAllFrames(executeGetFramesSelectionTextAndLanguageCode).then((framesSelectionTextAndLanguage) => {
Expand Down Expand Up @@ -569,15 +605,6 @@ const enablePopup = () => {
// NOTE: while not strictly necessary, keep and pass a reference to the global (initialized) synthesizer.
let synthesizer = null;

rootChain(
() => setup()
.then((result) => {
synthesizer = result;

return undefined;
})
);

const speakSelectionOnPage = () => promiseTry(
() => {
return Promise.all([
Expand Down Expand Up @@ -607,11 +634,6 @@ const enablePopup = () => {
}
);

// TODO: internal check to see if Talkie was speaking?
const isSpeaking = () => promiseTry(
() => synthesizer.speaking === true
);

const startStopSpeakSelectionOnPage = () => promiseTry(
() => {
return isSpeaking()
Expand Down Expand Up @@ -779,7 +801,7 @@ const enablePopup = () => {
return Promise.all(contextMenuOptionsPromises);
};

const onInstalledHandler = () => {
const onExtensionInstalledHandler = () => {
createContextMenus();
};

Expand All @@ -800,6 +822,54 @@ const enablePopup = () => {
});
};

const onTabRemovedHandler = (tabId) => {
return isSpeakingTabId(tabId)
.then((isTabSpeaking) => {
if (isTabSpeaking) {
return stopSpeaking(broadcaster, synthesizer)
.then(() => setTabIsDoneSpeaking(tabId));
}

return undefined;
});
};

const onTabUpdatedHandler = (tabId, changeInfo) => {
return isSpeakingTabId(tabId)
.then((isTabSpeaking) => {
// NOTE: changeInfo only has properties which have changed.
// https://developer.chrome.com/extensions/tabs#event-onUpdated
if (isTabSpeaking && changeInfo.url) {
return stopSpeaking(broadcaster, synthesizer)
.then(() => setTabIsDoneSpeaking(tabId));
}

return undefined;
});
};

const onExtensionSuspendHandler = () => {
log("Start", "onExtensionSuspendHandler");

return isSpeaking()
.then((talkieIsSpeaking) => {
// Clear all text if Talkie was speaking.
if (talkieIsSpeaking) {
return stopSpeaking();
}

return undefined;
})
.then(() => {
// Reset the system to resume playback, just to be nice to the world.
synthesizer.resume();

log("Done", "onExtensionSuspendHandler");

return undefined;
});
};

const broadcaster = new Broadcaster();
broadcaster.start();

Expand All @@ -808,9 +878,8 @@ const enablePopup = () => {

broadcaster.registerListeningAction(knownEvents.afterSpeaking, () => executePlugOnce());

broadcaster.registerListeningAction(knownEvents.beforeSpeaking, () => executeAddOnBeforeUnloadHandlers());
broadcaster.registerListeningAction(knownEvents.beforeSpeaking, () => executeSetTalkieIsSpeaking());
broadcaster.registerListeningAction(knownEvents.afterSpeaking, () => executeSetTalkieIsNotSpeaking());
broadcaster.registerListeningAction(knownEvents.beforeSpeaking, () => setActiveTabAsSpeaking());
broadcaster.registerListeningAction(knownEvents.afterSpeaking, () => setActiveTabIsDoneSpeaking());

// NOTE: setting icons async.
broadcaster.registerListeningAction(knownEvents.beforeSpeaking, () => { setTimeout(() => setIconModePlaying(), 10); return undefined; });
Expand All @@ -819,6 +888,9 @@ const enablePopup = () => {
broadcaster.registerListeningAction(knownEvents.beforeSpeaking, () => disablePopup());
broadcaster.registerListeningAction(knownEvents.afterSpeaking, () => enablePopup());

broadcaster.registerListeningAction(knownEvents.beforeSpeaking, () => preventSuspend());
broadcaster.registerListeningAction(knownEvents.afterSpeaking, () => allowSuspend());

const progress = new TalkieProgress(broadcaster);

progress.start()
Expand All @@ -834,7 +906,11 @@ const enablePopup = () => {
window.stopSpeakFromFrontend = stopSpeakingAction;
window.startSpeakFromFrontend = startSpeakingTextInVoiceAction;

chrome.runtime.onInstalled.addListener(onInstalledHandler);
chrome.runtime.onInstalled.addListener(onExtensionInstalledHandler);

chrome.tabs.onRemoved.addListener(onTabRemovedHandler);
chrome.tabs.onUpdated.addListener(onTabUpdatedHandler);
chrome.runtime.onSuspend.addListener(onExtensionSuspendHandler);

// NOTE: used when the popup has been disabled.
chrome.browserAction.onClicked.addListener(iconClickAction);
Expand All @@ -843,6 +919,15 @@ const enablePopup = () => {

chrome.contextMenus.onClicked.addListener(contextMenuClickAction);
enablePopup();

rootChain(
() => setup()
.then((result) => {
synthesizer = result;

return undefined;
})
);
}());

log("Done", "Loading code");
log("Done", "Loading backgrund code");
11 changes: 11 additions & 0 deletions src/shared.js
Expand Up @@ -207,6 +207,16 @@ const getCurrentActiveTab = () => new Promise(
}
);

const getCurrentActiveTabId = () => getCurrentActiveTab()
.then((activeTab) => {
if (activeTab) {
return activeTab.id;
}

// NOTE: some tabs can't be retreived.
return null;
});

const isCurrentPageInternalToTalkie = () => promiseTry(
() => getCurrentActiveTab()
.then((tab) => {
Expand Down Expand Up @@ -881,6 +891,7 @@ api.shared = {
flatten,
getCurrentActiveNormalLoadedTab,
getCurrentActiveTab,
getCurrentActiveTabId,
getMappedVoices,
getRandomInt,
getStoredValue,
Expand Down

0 comments on commit f61fe58

Please sign in to comment.