diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/calendar/base/modules/calAsyncUtils.jsm thunderbird-trunk-37.0~a1~hg20150107r17323.222415/calendar/base/modules/calAsyncUtils.jsm --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/calendar/base/modules/calAsyncUtils.jsm 2015-01-06 13:13:43.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/calendar/base/modules/calAsyncUtils.jsm 2015-01-07 22:14:55.000000000 +0000 @@ -67,7 +67,7 @@ * will be complaints that an argument is missing. */ promisifyCalendar: function(aCalendar) { - return Proxy(aCalendar, promisifyProxyHandler); + return new Proxy(aCalendar, promisifyProxyHandler); }, /** * Create an operation listener (calIOperationListener) that resolves when diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/calendar/base/src/calUtils.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/calendar/base/src/calUtils.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/calendar/base/src/calUtils.js 2015-01-06 13:13:43.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/calendar/base/src/calUtils.js 2015-01-07 22:14:56.000000000 +0000 @@ -1214,7 +1214,8 @@ try { iface[func].apply(iface, args ? args : []); } catch (exc) { - Components.utils.reportError(exc + "\nSTACK: " + exc.stack); + let stack = exc.stack || (exc.location ? exc.location.formattedStack : null); + Components.utils.reportError(exc + "\nSTACK: " + stack); } } this.mInterfaces.forEach(notifyFunc); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/debian/changelog thunderbird-trunk-37.0~a1~hg20150107r17323.222415/debian/changelog --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/debian/changelog 2015-01-06 13:41:45.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/debian/changelog 2015-01-07 22:54:18.000000000 +0000 @@ -1,10 +1,10 @@ -thunderbird-trunk (37.0~a1~hg20150106r17307.222089-0ubuntu1~umd1~trusty) trusty; urgency=medium +thunderbird-trunk (37.0~a1~hg20150107r17323.222415-0ubuntu1~umd1~trusty) trusty; urgency=medium * Refresh patches - update debian/patches/unity-menubar.patch - update debian/patches/dont-include-hyphenation-patterns.patch - -- Chris Coulson Mon, 08 Dec 2014 19:22:09 +0000 + -- Chris Coulson Wed, 07 Jan 2015 22:08:37 +0000 thunderbird-trunk (36.0~a1~hg20141126r17147.217518-0ubuntu1) utopic; urgency=medium diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/changesets thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/changesets --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/changesets 2015-01-06 13:17:57.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/changesets 2015-01-07 22:17:38.000000000 +0000 @@ -7,10 +7,10 @@ ca 671:0427dac6c811 cs 1966:9f298225fdd0 da 735:3da200622aa9 -de 3007:5a874cd81db3 +de 3008:ea13cdb0f61b el 356:6b23a751385f en-GB 444:876cf60b4ae5 -es-AR 937:468733829156 +es-AR 938:1562d452f929 es-ES 2325:d08982ade622 et 530:54f9fbfcd622 eu 379:d7df5b30dd48 @@ -24,7 +24,7 @@ hu 794:b45f6b60407e id 512:b520186368ec is 297:33af74865c25 -it 3572:42874962c3b0 +it 3576:b2e5691e68ae ja 633:608df4a0c172 ja-JP-mac 634:aba6f4f0ee5e ka 56:2ee9bc162267 @@ -39,9 +39,9 @@ pt-PT 2056:22b468633bda rm 388:945a12b3635b ro 414:9a349bbcf451 -ru 2259:112b37c18b04 +ru 2260:b1944c9224ca si 414:fb01300c5c4a -sk 1232:bce9afe2c04b +sk 1237:fd7ff14af866 sl 820:a5e5e63844e9 sq 774:4e1d2dc5d752 sr 254:e6fa777ae177 diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/de/dom/chrome/appstrings.properties thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/de/dom/chrome/appstrings.properties --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/de/dom/chrome/appstrings.properties 2015-01-06 13:15:50.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/de/dom/chrome/appstrings.properties 2015-01-07 22:16:18.000000000 +0000 @@ -30,6 +30,6 @@ externalProtocolLaunchBtn=Anwendung ausführen malwareBlocked=Die Seite auf %S wurde als attackierende Seite gemeldet und auf Grund Ihrer Sicherheitseinstellungen blockiert. phishingBlocked=Die Website auf %S ist als eine gefälschte Seite gemeldet, die erstellt wurde, um Anwender dazu zu verleiten, persönliche oder finanzielle Daten preiszugeben. -cspBlocked=Diese Seite hat eine Inhaltsichterheitsrichtlinie (Content Security Policy), die verhindert, dass sie auf diese Art geladen wird. +cspBlocked=Diese Seite hat eine Inhaltsicherheitsrichtlinie (Content Security Policy), die verhindert, dass sie auf diese Art geladen wird. corruptedContentError=Die Seite, die Sie anzusehen versuchen, kann nicht angezeigt werden, da ein Fehler in der Datenübertragung festgestellt wurde. remoteXUL=Diese Seite verwendet eine nicht unterstützte Technologie, die nicht mehr standardmäßig unterstützt wird. diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/de/dom/chrome/netError.dtd thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/de/dom/chrome/netError.dtd --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/de/dom/chrome/netError.dtd 2015-01-06 13:15:50.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/de/dom/chrome/netError.dtd 2015-01-07 22:16:18.000000000 +0000 @@ -87,8 +87,8 @@

Diese Arten von Web-Fälschungen werden in Betrugsversuchen verwendet, die als "Phishing-Attacken" bekannt sind. Dabei versuchen Kriminelle in betrügerischer Absicht, Webseiten oder E-Mails nachzuahmen, denen Sie eventuell vertrauen.

"> - -Der Browser hat das Laden dieser Seite auf diese Art blockiert, weil diese Seite eine Inhaltsichterheitsrichtlinie (Content Security Policy) hat, das nicht erlaubt.

"> + +Der Browser hat das Laden dieser Seite auf diese Art blockiert, weil diese Seite eine Inhaltsicherheitsrichtlinie (Content Security Policy) hat, das nicht erlaubt.

"> Die Seite, die Sie anzusehen versuchen, kann nicht angezeigt werden, da ein Fehler in der Datenübertragung festgestellt wurde.

"> diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/es-AR/chat/xmpp.properties thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/es-AR/chat/xmpp.properties --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/es-AR/chat/xmpp.properties 2015-01-06 13:16:03.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/es-AR/chat/xmpp.properties 2015-01-07 22:16:24.000000000 +0000 @@ -44,6 +44,9 @@ # fails. # %S is the name of the MUC. conversation.error.joinFailed=No se pudo unir: %S +# These are displayed in a conversation as a system error message. +conversation.error.remoteServerNotFound=No se pudo alcanzar el servidor del destinatario +conversation.error.unknownError=Error desconocido # LOCALIZATION NOTE (tooltip.*): # These are the titles of lines of information that will appear in diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/ru/chat/xmpp.properties thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/ru/chat/xmpp.properties --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/ru/chat/xmpp.properties 2015-01-06 13:17:04.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/ru/chat/xmpp.properties 2015-01-07 22:17:16.000000000 +0000 @@ -25,6 +25,8 @@ connection.error.failedToGetAResource = Не удалось получить ресурс conversation.error.notDelivered = Это сообщение не может быть доставлено: %S conversation.error.joinFailed = Не удалось присоединиться: %S +conversation.error.remoteServerNotFound = Не удалось связаться с сервером получателя +conversation.error.unknownError = Неизвестная ошибка tooltip.status = Статус (%S) tooltip.statusNoResource = Статус tooltip.subscription = Подписка diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/ru/mail/chrome/messenger/preferences/advanced.dtd thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/ru/mail/chrome/messenger/preferences/advanced.dtd --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/ru/mail/chrome/messenger/preferences/advanced.dtd 2015-01-06 13:17:04.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/ru/mail/chrome/messenger/preferences/advanced.dtd 2015-01-07 22:17:16.000000000 +0000 @@ -12,6 +12,8 @@ + + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/sk/chat/xmpp.properties thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/sk/chat/xmpp.properties --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/l10n/sk/chat/xmpp.properties 2015-01-06 13:17:07.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/l10n/sk/chat/xmpp.properties 2015-01-07 22:17:20.000000000 +0000 @@ -43,6 +43,9 @@ # fails. # %S is the name of the MUC. conversation.error.joinFailed=Nebolo možné sa pripojiť k %S +# These are displayed in a conversation as a system error message. +conversation.error.remoteServerNotFound=Nebolo možné spojiť sa so serverom príjemcu +conversation.error.unknownError=Neznáma chyba # LOCALIZATION NOTE (tooltip.*): # These are the titles of lines of information that will appear in diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mail/components/compose/content/addressingWidgetOverlay.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mail/components/compose/content/addressingWidgetOverlay.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mail/components/compose/content/addressingWidgetOverlay.js 2015-01-06 13:13:43.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mail/components/compose/content/addressingWidgetOverlay.js 2015-01-07 22:14:56.000000000 +0000 @@ -119,7 +119,6 @@ var addrReply = ""; var addrNg = ""; var addrFollow = ""; - var addrOther = ""; var to_Sep = ""; var cc_Sep = ""; var bcc_Sep = ""; @@ -168,9 +167,12 @@ case "addr_reply" : addrReply += reply_Sep + recipient; reply_Sep = ","; break; case "addr_newsgroups" : addrNg += ng_Sep + fieldValue; ng_Sep = ","; break; case "addr_followup" : addrFollow += follow_Sep + fieldValue; follow_Sep = ","; break; - // do CRLF, same as PUSH_NEWLINE() in nsMsgSend.h / nsMsgCompUtils.cpp - // see bug #195965 - case "addr_other" : addrOther += awGetPopupElement(i).selectedItem.getAttribute("label") + " " + fieldValue + "\r\n";break; + case "addr_other": + let headerName = + awGetPopupElement(i).selectedItem.getAttribute("label"); + headerName = headerName.substring(0, headerName.indexOf(':')); + msgCompFields.setHeader(headerName, fieldValue); + break; } } i ++; @@ -182,7 +184,6 @@ msgCompFields.replyTo = addrReply; msgCompFields.newsgroups = addrNg; msgCompFields.followupTo = addrFollow; - msgCompFields.otherRandomHeaders = addrOther; } else dump("Message Compose Error: msgCompFields is null (ExtractRecipients)"); @@ -204,7 +205,6 @@ let msgTo = msgCompFields.to; let msgCC = msgCompFields.cc; let msgBCC = msgCompFields.bcc; - let msgRandomHeaders = msgCompFields.otherRandomHeaders; let msgNewsgroups = msgCompFields.newsgroups; let msgFollowupTo = msgCompFields.followupTo; let havePrimaryRecipient = false; @@ -226,8 +226,6 @@ if (msgBCC) awSetInputAndPopupFromArray(msgCompFields.splitRecipients(msgBCC, false, {}), "addr_bcc", newListBoxNode, templateNode); - if (msgRandomHeaders) - awSetInputAndPopup(msgRandomHeaders, "addr_other", newListBoxNode, templateNode); if (msgNewsgroups) { awSetInputAndPopup(msgNewsgroups, "addr_newsgroups", newListBoxNode, templateNode); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mail/test/mozmill/composition/test-send-button.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mail/test/mozmill/composition/test-send-button.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mail/test/mozmill/composition/test-send-button.js 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mail/test/mozmill/composition/test-send-button.js 2015-01-07 22:14:56.000000000 +0000 @@ -66,8 +66,8 @@ // On an empty window, Send must be disabled. check_send_commands_state(cwc, false); - // On any (even invalid) "To:" addressee input, Send must be enabled. - setupComposeWin(cwc, "recipient", "", ""); + // On valid "To:" addressee input, Send must be enabled. + setupComposeWin(cwc, "recipient@fake.invalid", "", ""); check_send_commands_state(cwc, true); // When the addressee is not in To, Cc, Bcc or Newsgroup, disable Send again. diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/public/nsIMsgCompFields.idl thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/public/nsIMsgCompFields.idl --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/public/nsIMsgCompFields.idl 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/public/nsIMsgCompFields.idl 2015-01-07 22:14:56.000000000 +0000 @@ -3,12 +3,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "nsISupports.idl" -#include "nsIMsgAttachment.idl" -#include "nsISimpleEnumerator.idl" +#include "msgIStructuredHeaders.idl" -[scriptable, uuid(4f803e7a-5bd5-4344-9901-7a9ee89be780)] -interface nsIMsgCompFields : nsISupports { +interface nsIMsgAttachment; +interface nsISimpleEnumerator; + +/** + * A collection of headers and other attributes for building a mail message. + */ +[scriptable, uuid(4d251187-8213-467a-90f7-f8f330f59085)] +interface nsIMsgCompFields : msgIWritableStructuredHeaders { attribute AString from; attribute AString replyTo; @@ -47,8 +51,6 @@ /// Status of manually-activated attachment reminder. attribute boolean attachmentReminder; - attribute AString otherRandomHeaders; - /** * Beware that when setting this property, your body must be properly wrapped, * and the line endings must match MSG_LINEBREAK, namely "\r\n" on Windows @@ -76,9 +78,6 @@ void ConvertBodyToPlainText(); - // Check if the composing mail headers can be converted to a mail charset. - boolean checkCharsetConversion(out string fallbackCharset); - /** * Indicates whether we need to check if the current |DocumentCharset| * can represent all the characters in the message body. It should be diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompFields.cpp thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompFields.cpp --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompFields.cpp 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompFields.cpp 2015-01-07 22:14:56.000000000 +0000 @@ -9,24 +9,58 @@ #include "nsMsgUtils.h" #include "prmem.h" #include "nsIFileChannel.h" +#include "nsIMsgAttachment.h" #include "nsIMsgMdnGenerator.h" #include "nsServiceManagerUtils.h" #include "nsMsgMimeCID.h" #include "nsArrayEnumerator.h" #include "nsMemory.h" +#include "mozilla/ArrayUtils.h" #include "mozilla/mailnews/MimeHeaderParser.h" using namespace mozilla::mailnews; -/* the following macro actually implement addref, release and query interface for our component. */ -NS_IMPL_ISUPPORTS(nsMsgCompFields, nsIMsgCompFields) - +struct HeaderInfo { + /// Header name + const char *mName; + /// If true, nsMsgCompFields should reflect the raw header value instead of + /// the unstructured header value. + bool mStructured; +}; + +// This is a mapping of the m_headers local set to the actual header name we +// store on the structured header object. +static HeaderInfo kHeaders[] = { + { "From", true }, + { "Reply-To", true }, + { "To", true }, + { "Cc", true }, + { "Bcc", true }, + { nullptr, false }, + { nullptr, false }, + { "Newsgroups", true }, + { "Followup-To", true }, + { "Subject", false }, + { "Organization", false }, + { "References", true }, + { "X-Mozilla-News-Host", false }, + { "X-Priority", false }, + { nullptr, false }, + { "Message-Id", true }, + { "X-Template", true }, + { nullptr, false } +}; + +static_assert(MOZ_ARRAY_LENGTH(kHeaders) == + nsMsgCompFields::MSG_MAX_HEADERS, + "These two arrays need to be kept in sync or bad things will happen!"); + +NS_IMPL_ISUPPORTS(nsMsgCompFields, nsIMsgCompFields, msgIStructuredHeaders, + msgIWritableStructuredHeaders) + nsMsgCompFields::nsMsgCompFields() +: mStructuredHeaders(do_CreateInstance(NS_ISTRUCTUREDHEADERS_CONTRACTID)) { - int16_t i; - for (i = 0; i < MSG_MAX_HEADERS; i ++) - m_headers[i] = nullptr; - m_body.Truncate(); m_attachVCard = false; @@ -51,9 +85,6 @@ nsMsgCompFields::~nsMsgCompFields() { - int16_t i; - for (i = 0; i < MSG_MAX_HEADERS; i ++) - PR_FREEIF(m_headers[i]); } nsresult nsMsgCompFields::SetAsciiHeader(MsgHeaderID header, const char *value) @@ -61,27 +92,23 @@ NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS, "Invalid message header index!"); - nsresult rv = NS_OK; - char* old = m_headers[header]; /* Done with careful paranoia, in case the - value given is the old value (or worse, - a substring of the old value, as does - happen here and there.) - */ - if (value != old) + // If we are storing this on the structured header object, we need to set the + // value on that object as well. Note that the value may be null, which we'll + // take as an attempt to delete the header. + const char *headerName = kHeaders[header].mName; + if (headerName) { - if (value) - { - m_headers[header] = strdup(value); - if (!m_headers[header]) - rv = NS_ERROR_OUT_OF_MEMORY; - } - else - m_headers[header] = nullptr; + if (!value || !*value) + return mStructuredHeaders->DeleteHeader(headerName); - PR_FREEIF(old); + return mStructuredHeaders->SetRawHeader(headerName, + nsDependentCString(value), "UTF-8"); } - return rv; + // Not on the structurd header object, so save it locally. + m_headers[header] = value; + + return NS_OK; } const char* nsMsgCompFields::GetAsciiHeader(MsgHeaderID header) @@ -89,7 +116,24 @@ NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS, "Invalid message header index!"); - return m_headers[header] ? m_headers[header] : ""; + const char *headerName = kHeaders[header].mName; + if (headerName) + { + // We may be out of sync with the structured header object. Retrieve the + // header value. + if (kHeaders[header].mStructured) + { + mStructuredHeaders->GetRawHeader(headerName, m_headers[header]); + } + else + { + nsString value; + mStructuredHeaders->GetUnstructuredHeader(headerName, value); + CopyUTF16toUTF8(value, m_headers[header]); + } + } + + return m_headers[header].get(); } nsresult nsMsgCompFields::SetUnicodeHeader(MsgHeaderID header, const nsAString& value) @@ -235,16 +279,6 @@ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } -NS_IMETHODIMP nsMsgCompFields::SetOtherRandomHeaders(const nsAString &value) -{ - return SetUnicodeHeader(MSG_OTHERRANDOMHEADERS_HEADER_ID, value); -} - -NS_IMETHODIMP nsMsgCompFields::GetOtherRandomHeaders(nsAString &_retval) -{ - return GetUnicodeHeader(MSG_OTHERRANDOMHEADERS_HEADER_ID, _retval); -} - NS_IMETHODIMP nsMsgCompFields::SetNewspostUrl(const char *value) { return SetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID, value); @@ -521,7 +555,7 @@ ExtractEmails(header, results); else ExtractDisplayAddresses(header, results); - + uint32_t count = results.Length(); char16_t **result = (char16_t **)NS_Alloc(sizeof(char16_t *) * count); for (uint32_t i = 0; i < count; ++i) @@ -592,21 +626,6 @@ return *aDefaultCharacterSet ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } -NS_IMETHODIMP nsMsgCompFields::CheckCharsetConversion(char **fallbackCharset, bool *_retval) -{ - NS_ENSURE_ARG_POINTER(_retval); - - nsAutoCString headers; - for (int16_t i = 0; i < MSG_MAX_HEADERS; i++) - headers.Append(m_headers[i]); - - // charset conversion check - *_retval = nsMsgI18Ncheck_data_in_charset_range(GetCharacterSet(), NS_ConvertUTF8toUTF16(headers.get()).get(), - fallbackCharset); - - return NS_OK; -} - NS_IMETHODIMP nsMsgCompFields::GetNeedToCheckCharset(bool *_retval) { NS_ENSURE_ARG_POINTER(_retval); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompFields.h thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompFields.h --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompFields.h 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompFields.h 2015-01-07 22:14:56.000000000 +0000 @@ -33,8 +33,15 @@ /* this macro defines QueryInterface, AddRef and Release for this class */ NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_MSGISTRUCTUREDHEADERS(mStructuredHeaders->) + NS_FORWARD_MSGIWRITABLESTRUCTUREDHEADERS(mStructuredHeaders->) NS_DECL_NSIMSGCOMPFIELDS + // Allow the C++ utility methods for people who use a concrete class instead + // of the interfaces. + using msgIStructuredHeaders::GetAddressingHeader; + using msgIWritableStructuredHeaders::SetAddressingHeader; + typedef enum MsgHeaderID { MSG_FROM_HEADER_ID = 0, @@ -49,7 +56,6 @@ MSG_SUBJECT_HEADER_ID, MSG_ORGANIZATION_HEADER_ID, MSG_REFERENCES_HEADER_ID, - MSG_OTHERRANDOMHEADERS_HEADER_ID, MSG_NEWSPOSTURL_HEADER_ID, MSG_PRIORITY_HEADER_ID, MSG_CHARACTER_SET_HEADER_ID, @@ -107,9 +113,6 @@ const char* GetReferences() {return GetAsciiHeader(MSG_REFERENCES_HEADER_ID);} - nsresult SetOtherRandomHeaders(const char *value) {return SetAsciiHeader(MSG_OTHERRANDOMHEADERS_HEADER_ID, value);} - const char* GetOtherRandomHeaders() {return GetAsciiHeader(MSG_OTHERRANDOMHEADERS_HEADER_ID);} - const char* GetNewspostUrl() {return GetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID);} const char* GetPriority() {return GetAsciiHeader(MSG_PRIORITY_HEADER_ID);} @@ -140,7 +143,7 @@ protected: virtual ~nsMsgCompFields(); - char* m_headers[MSG_MAX_HEADERS]; + nsCString m_headers[MSG_MAX_HEADERS]; nsCString m_body; nsCOMArray m_attachments; bool m_attachVCard; @@ -156,6 +159,7 @@ bool m_needToCheckCharset; nsCOMPtr mSecureCompFields; + nsCOMPtr mStructuredHeaders; }; diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompose.cpp thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompose.cpp --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompose.cpp 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompose.cpp 2015-01-07 22:14:56.000000000 +0000 @@ -63,6 +63,7 @@ #include "nsUConvCID.h" #include "nsIUnicodeNormalizer.h" #include "nsIMsgAccountManager.h" +#include "nsIMsgAttachment.h" #include "nsIMsgProgress.h" #include "nsMsgFolderFlags.h" #include "nsIMsgDatabase.h" @@ -5376,34 +5377,12 @@ NS_ENSURE_ARG_POINTER(identity); NS_ENSURE_ARG_POINTER(_retval); - nsresult rv = m_compFields->CheckCharsetConversion(fallbackCharset, _retval); - NS_ENSURE_SUCCESS(rv, rv); - - if (*_retval) - { - nsString fullName; - nsString organization; - nsAutoString identityStrings; - - rv = identity->GetFullName(fullName); - NS_ENSURE_SUCCESS(rv, rv); - if (!fullName.IsEmpty()) - identityStrings.Append(fullName); - - rv = identity->GetOrganization(organization); - NS_ENSURE_SUCCESS(rv, rv); - if (!organization.IsEmpty()) - identityStrings.Append(organization); - - if (!identityStrings.IsEmpty()) - { - // use fallback charset if that's already set - const char *charset = (fallbackCharset && *fallbackCharset) ? *fallbackCharset : m_compFields->GetCharacterSet(); - *_retval = nsMsgI18Ncheck_data_in_charset_range(charset, identityStrings.get(), - fallbackCharset); - } - } - + // Kept around for legacy reasons. This method is supposed to check that the + // headers can be converted to the appropriate charset, but we don't support + // encoding headers to non-UTF-8, so this is now moot. + if (fallbackCharset) + *fallbackCharset = nullptr; + *_retval = true; return NS_OK; } diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompUtils.cpp thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompUtils.cpp --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompUtils.cpp 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompUtils.cpp 2015-01-07 22:14:56.000000000 +0000 @@ -29,8 +29,10 @@ #include "nsMemory.h" #include "nsCRTGlue.h" #include +#include "mozilla/mailnews/Services.h" #include "mozilla/Services.h" #include "nsIMIMEInfo.h" +#include "nsIMsgHeaderParser.h" NS_IMPL_ISUPPORTS(nsMsgCompUtils, nsIMsgCompUtils) @@ -222,170 +224,76 @@ return mime_sanity_check_fields_recipients(to, cc, bcc, newsgroups); } -static char * -nsMsgStripLine (char * string) -{ - char * ptr; - - /* remove leading blanks */ - while(*string=='\t' || *string==' ' || *string=='\r' || *string=='\n') - string++; - - for(ptr=string; *ptr; ptr++) - ; /* NULL BODY; Find end of string */ - - /* remove trailing blanks */ - for(ptr--; ptr >= string; ptr--) - { - if(*ptr=='\t' || *ptr==' ' || *ptr=='\r' || *ptr=='\n') - *ptr = '\0'; - else - break; - } - - return string; -} - // // Generate the message headers for the new RFC822 message // #define UA_PREF_PREFIX "general.useragent." -#define ENCODE_AND_PUSH(name, structured, body, charset, usemime) \ - { \ - PUSH_STRING((name)); \ - convbuf = nsMsgI18NEncodeMimePartIIStr((body), (structured), (charset), strlen(name), (usemime)); \ - if (convbuf) { \ - PUSH_STRING (convbuf); \ - PR_FREEIF(convbuf); \ - } \ - else \ - PUSH_STRING((body)); \ - PUSH_NEWLINE (); \ - } - -char * -mime_generate_headers (nsMsgCompFields *fields, - const char *charset, - nsMsgDeliverMode deliver_mode, nsIPrompt * aPrompt, nsresult *status) +// Helper macro for generating the X-Mozilla-Draft-Info header. +#define APPEND_BOOL(method, param) \ + do { \ + bool val = false; \ + fields->Get##method(&val); \ + if (val) \ + draftInfo.AppendLiteral(param "=1"); \ + else \ + draftInfo.AppendLiteral(param "=0"); \ + } while (false) + +nsresult mime_generate_headers(nsIMsgCompFields *fields, + nsMsgDeliverMode deliver_mode, + msgIWritableStructuredHeaders *finalHeaders) { - nsresult rv; - *status = NS_OK; + nsresult rv = NS_OK; nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); - if (NS_FAILED(rv)) { - *status = rv; - return nullptr; - } + NS_ENSURE_SUCCESS(rv, rv); - bool usemime = nsMsgMIMEGetConformToStandard(); - int32_t size = 0; - char *buffer = nullptr, *buffer_tail = nullptr; bool isDraft = deliver_mode == nsIMsgSend::nsMsgSaveAsDraft || deliver_mode == nsIMsgSend::nsMsgSaveAsTemplate || deliver_mode == nsIMsgSend::nsMsgQueueForLater || deliver_mode == nsIMsgSend::nsMsgDeliverBackground; - const char* pFrom; - const char* pTo; - const char* pCc; - const char* pMessageID; - const char* pReplyTo; - const char* pOrg; - const char* pNewsGrp; - const char* pFollow; - const char* pSubject; - const char* pPriority; - const char* pReference; - const char* pOtherHdr; - char *convbuf; - bool hasDisclosedRecipient = false; - nsAutoCString headerBuf; // accumulate header strings to get length - headerBuf.Truncate(); + MOZ_ASSERT(fields, "null fields"); + NS_ENSURE_ARG_POINTER(fields); - NS_ASSERTION (fields, "null fields"); - if (!fields) { - *status = NS_ERROR_NULL_POINTER; - return nullptr; - } - pFrom = fields->GetFrom(); - if (pFrom) - headerBuf.Append(pFrom); - pReplyTo =fields->GetReplyTo(); - if (pReplyTo) - headerBuf.Append(pReplyTo); - pTo = fields->GetTo(); - if (pTo) - headerBuf.Append(pTo); - pCc = fields->GetCc(); - if (pCc) - headerBuf.Append(pCc); - pNewsGrp = fields->GetNewsgroups(); if (pNewsGrp) size += 3 * PL_strlen (pNewsGrp); - pFollow= fields->GetFollowupTo(); if (pFollow) size += 3 * PL_strlen (pFollow); - pSubject = fields->GetSubject(); - if (pSubject) - headerBuf.Append(pSubject); - pReference = fields->GetReferences(); if (pReference) size += 3 * PL_strlen (pReference); - pOrg= fields->GetOrganization(); - if (pOrg) - headerBuf.Append(pOrg); - pOtherHdr= fields->GetOtherRandomHeaders(); if (pOtherHdr) size += 3 * PL_strlen (pOtherHdr); - pPriority = fields->GetPriority(); if (pPriority) size += 3 * PL_strlen (pPriority); - pMessageID = fields->GetMessageId(); if (pMessageID) size += PL_strlen (pMessageID); - - /* Multiply by 3 here to make enough room for MimePartII conversion */ - size += 3 * headerBuf.Length(); - - /* Add a bunch of slop for the static parts of the headers. */ - /* size += 2048; */ - size += 2560; - - buffer = (char *) PR_Malloc (size); - if (!buffer) { - *status = NS_ERROR_OUT_OF_MEMORY; - return nullptr; /* NS_ERROR_OUT_OF_MEMORY */ - } + nsCOMArray from; + fields->GetAddressingHeader("From", from, true); - buffer_tail = buffer; + // Copy all headers from the original compose field. + rv = finalHeaders->AddAllHeaders(fields); + NS_ENSURE_SUCCESS(rv, rv); - if (pMessageID && *pMessageID) { - PUSH_STRING ("Message-ID: "); - PUSH_STRING (pMessageID); - PUSH_NEWLINE (); + bool hasMessageId = false; + if (NS_SUCCEEDED(fields->HasHeader("Message-ID", &hasMessageId)) && + hasMessageId) + { /* MDN request header requires to have MessageID header presented * in the message in order to * coorelate the MDN reports to the original message. Here will be * the right place */ - if (fields->GetReturnReceipt() && + bool returnReceipt = false; + fields->GetReturnReceipt(&returnReceipt); + if (returnReceipt && (deliver_mode != nsIMsgSend::nsMsgSaveAsDraft && deliver_mode != nsIMsgSend::nsMsgSaveAsTemplate)) { - int32_t receipt_header_type = nsIMsgMdnGenerator::eDntType; - fields->GetReceiptHeaderType(&receipt_header_type); + int32_t receipt_header_type = nsIMsgMdnGenerator::eDntType; + fields->GetReceiptHeaderType(&receipt_header_type); // nsIMsgMdnGenerator::eDntType = MDN Disposition-Notification-To: ; // nsIMsgMdnGenerator::eRrtType = Return-Receipt-To: ; // nsIMsgMdnGenerator::eDntRrtType = both MDN DNT and RRT headers . if (receipt_header_type != nsIMsgMdnGenerator::eRrtType) - ENCODE_AND_PUSH( - "Disposition-Notification-To: ", true, pFrom, charset, usemime); + finalHeaders->SetAddressingHeader("Disposition-Notification-To", from); if (receipt_header_type != nsIMsgMdnGenerator::eDntType) - ENCODE_AND_PUSH( - "Return-Receipt-To: ", true, pFrom, charset, usemime); - } - -#ifdef SUPPORT_X_TEMPLATE_NAME - if (deliver_mode == MSG_SaveAsTemplate) { - const char *pStr = fields->GetTemplateName(); - pStr = pStr ? pStr : ""; - ENCODE_AND_PUSH("X-Template: ", false, pStr, charset, usemime); + finalHeaders->SetAddressingHeader("Return-Receipt-To", from); } -#endif /* SUPPORT_X_TEMPLATE_NAME */ } PRExplodedTime now; @@ -397,74 +305,49 @@ PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as per RFC 1123 (superceding RFC 822.) */ - PR_FormatTimeUSEnglish(buffer_tail, 100, - "Date: %a, %d %b %Y %H:%M:%S ", + char dateString[130]; + PR_FormatTimeUSEnglish(dateString, sizeof(dateString), + "%a, %d %b %Y %H:%M:%S ", &now); - buffer_tail += PL_strlen (buffer_tail); - PR_snprintf(buffer_tail, buffer + size - buffer_tail, + char *entryPoint = dateString + strlen(dateString); + PR_snprintf(entryPoint, sizeof(dateString) - (entryPoint - dateString), "%c%02d%02d" CRLF, (gmtoffset >= 0 ? '+' : '-'), ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60), ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60)); - buffer_tail += PL_strlen (buffer_tail); - - if (pFrom && *pFrom) - { - ENCODE_AND_PUSH("From: ", true, pFrom, charset, usemime); - } - - if (pReplyTo && *pReplyTo) - { - ENCODE_AND_PUSH("Reply-To: ", true, pReplyTo, charset, usemime); - } - - if (pOrg && *pOrg) - { - ENCODE_AND_PUSH("Organization: ", false, pOrg, charset, usemime); - } + finalHeaders->SetRawHeader("Date", nsDependentCString(dateString), nullptr); // X-Mozilla-Draft-Info if (isDraft) { - PUSH_STRING(HEADER_X_MOZILLA_DRAFT_INFO); - PUSH_STRING(": internal/draft; "); - if (fields->GetAttachVCard()) - PUSH_STRING("vcard=1"); - else - PUSH_STRING("vcard=0"); - PUSH_STRING("; "); - if (fields->GetReturnReceipt()) { + nsAutoCString draftInfo; + draftInfo.AppendLiteral("internal/draft; "); + APPEND_BOOL(AttachVCard, "vcard"); + draftInfo.AppendLiteral("; "); + bool hasReturnReceipt = false; + fields->GetReturnReceipt(&hasReturnReceipt); + if (hasReturnReceipt) + { // slight change compared to 4.x; we used to use receipt= to tell // whether the draft/template has request for either MDN or DNS or both // return receipt; since the DNS is out of the picture we now use the // header type + 1 to tell whether user has requested the return receipt int32_t headerType = 0; fields->GetReceiptHeaderType(&headerType); - char *type = PR_smprintf("%d", (int)headerType + 1); - if (type) - { - PUSH_STRING("receipt="); - PUSH_STRING(type); - PR_FREEIF(type); - } + draftInfo.AppendLiteral("receipt="); + draftInfo.AppendInt(headerType + 1); } else - PUSH_STRING("receipt=0"); - PUSH_STRING("; "); - if (fields->GetDSN()) - PUSH_STRING("DSN=1"); - else - PUSH_STRING("DSN=0"); - PUSH_STRING("; "); - PUSH_STRING("uuencode=0"); - PUSH_STRING("; "); - if (fields->GetAttachmentReminder()) - PUSH_STRING("attachmentreminder=1"); - else - PUSH_STRING("attachmentreminder=0"); + draftInfo.AppendLiteral("receipt=0"); + draftInfo.AppendLiteral("; "); + APPEND_BOOL(DSN, "DSN"); + draftInfo.AppendLiteral("; "); + draftInfo.AppendLiteral("uuencode=0"); + draftInfo.AppendLiteral("; "); + APPEND_BOOL(AttachmentReminder, "attachmentreminder"); - PUSH_NEWLINE (); + finalHeaders->SetRawHeader(HEADER_X_MOZILLA_DRAFT_INFO, draftInfo, nullptr); } @@ -475,152 +358,69 @@ pHTTPHandler->GetUserAgent(userAgentString); if (!userAgentString.IsEmpty()) - { - PUSH_STRING ("User-Agent: "); - PUSH_STRING(userAgentString.get()); - PUSH_NEWLINE (); - } + finalHeaders->SetUnstructuredHeader("User-Agent", + NS_ConvertUTF8toUTF16(userAgentString)); } - PUSH_STRING ("MIME-Version: 1.0" CRLF); + finalHeaders->SetUnstructuredHeader("MIME-Version", NS_LITERAL_STRING("1.0")); - if (pNewsGrp && *pNewsGrp) { - /* turn whitespace into a comma list - */ - char *duppedNewsGrp = PL_strdup(pNewsGrp); - if (!duppedNewsGrp) { - PR_FREEIF(buffer); - *status = NS_ERROR_OUT_OF_MEMORY; - return nullptr; /* NS_ERROR_OUT_OF_MEMORY */ - } - char *n2 = nsMsgStripLine(duppedNewsGrp); - - for(char *ptr = n2; *ptr != '\0'; ptr++) { - /* find first non white space */ - while(!IS_SPACE(*ptr) && *ptr != ',' && *ptr != '\0') - ptr++; - - if(*ptr == '\0') - break; - - if(*ptr != ',') - *ptr = ','; - - /* find next non white space */ - char *ptr2 = ptr+1; - while(IS_SPACE(*ptr2)) - ptr2++; - - if(ptr2 != ptr+1) - PL_strcpy(ptr+1, ptr2); - } - - // we need to decide the Newsgroup related headers - // to write to the outgoing message. In ANY case, we need to write the - // "Newsgroup" header which is the "proper" header as opposed to the - // HEADER_X_MOZILLA_NEWSHOST which can contain the "news:" URL's. - // - // Since n2 can contain data in the form of: + nsAutoCString newsgroups; + finalHeaders->GetRawHeader("Newsgroups", newsgroups); + if (!newsgroups.IsEmpty()) + { + // Since the newsgroup header can contain data in the form of: // "news://news.mozilla.org/netscape.test,news://news.mozilla.org/netscape.junk" // we need to turn that into: "netscape.test,netscape.junk" - // - nsCOMPtr nntpService = do_GetService("@mozilla.org/messenger/nntpservice;1", &rv); - if (NS_FAILED(rv) || !nntpService) { - *status = NS_ERROR_FAILURE; - return nullptr; - } + // (XXX: can it really?) + nsCOMPtr nntpService = + do_GetService("@mozilla.org/messenger/nntpservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); nsCString newsgroupsHeaderVal; nsCString newshostHeaderVal; - rv = nntpService->GenerateNewsHeaderValsForPosting(nsDependentCString(n2), getter_Copies(newsgroupsHeaderVal), getter_Copies(newshostHeaderVal)); - if (NS_FAILED(rv)) - { - *status = rv; - return nullptr; - } - - // fixme:the newsgroups header had better be encoded as the server-side - // character encoding, but this |charset| might be different from it. - ENCODE_AND_PUSH("Newsgroups: ", false, newsgroupsHeaderVal.get(), - charset, false); + rv = nntpService->GenerateNewsHeaderValsForPosting(newsgroups, + getter_Copies(newsgroupsHeaderVal), getter_Copies(newshostHeaderVal)); + NS_ENSURE_SUCCESS(rv, rv); + finalHeaders->SetRawHeader("Newsgroups", newsgroupsHeaderVal, nullptr); // If we are here, we are NOT going to send this now. (i.e. it is a Draft, // Send Later file, etc...). Because of that, we need to store what the user // typed in on the original composition window for use later when rebuilding // the headers - if (deliver_mode != nsIMsgSend::nsMsgDeliverNow && deliver_mode != nsIMsgSend::nsMsgSendUnsent) + if (deliver_mode != nsIMsgSend::nsMsgDeliverNow && + deliver_mode != nsIMsgSend::nsMsgSendUnsent) { // This is going to be saved for later, that means we should just store // what the user typed into the "Newsgroup" line in the HEADER_X_MOZILLA_NEWSHOST // header for later use by "Send Unsent Messages", "Drafts" or "Templates" - PUSH_STRING (HEADER_X_MOZILLA_NEWSHOST); - PUSH_STRING (": "); - PUSH_STRING (newshostHeaderVal.get()); - PUSH_NEWLINE (); - } - - PR_FREEIF(duppedNewsGrp); - hasDisclosedRecipient = true; - } - - /* #### shamelessly duplicated from above */ - if (pFollow && *pFollow) { - /* turn whitespace into a comma list - */ - char *duppedFollowup = PL_strdup(pFollow); - if (!duppedFollowup) { - PR_FREEIF(buffer); - return nullptr; /* NS_ERROR_OUT_OF_MEMORY */ - } - char *n2 = nsMsgStripLine (duppedFollowup); - - for (char *ptr = n2; *ptr != '\0'; ptr++) { - /* find first non white space */ - while(!IS_SPACE(*ptr) && *ptr != ',' && *ptr != '\0') - ptr++; - - if(*ptr == '\0') - break; - - if(*ptr != ',') - *ptr = ','; - - /* find next non white space */ - char *ptr2 = ptr+1; - while(IS_SPACE(*ptr2)) - ptr2++; - - if(ptr2 != ptr+1) - PL_strcpy(ptr+1, ptr2); + finalHeaders->SetRawHeader(HEADER_X_MOZILLA_NEWSHOST, newshostHeaderVal, + nullptr); } - ENCODE_AND_PUSH("Followup-To: ", false, n2, charset, false); - PR_Free (duppedFollowup); - } - - if (pTo && *pTo) { - ENCODE_AND_PUSH("To: ", true, pTo, charset, usemime); + // Newsgroups are a recipient... hasDisclosedRecipient = true; } - if (pCc && *pCc) { - ENCODE_AND_PUSH("CC: ", true, pCc, charset, usemime); - hasDisclosedRecipient = true; - } + nsCOMArray recipients; + finalHeaders->GetAddressingHeader("To", recipients); + hasDisclosedRecipient |= !recipients.IsEmpty(); + finalHeaders->GetAddressingHeader("Cc", recipients); + hasDisclosedRecipient |= !recipients.IsEmpty(); // If we don't have disclosed recipient (only Bcc), address the message to // undisclosed-recipients to prevent problem with some servers // If we are saving the message as a draft, don't bother inserting the undisclosed recipients field. We'll take care of that when we // really send the message. - if (!hasDisclosedRecipient && !isDraft) + if (!hasDisclosedRecipient && !isDraft) { bool bAddUndisclosedRecipients = true; prefs->GetBoolPref("mail.compose.add_undisclosed_recipients", &bAddUndisclosedRecipients); if (bAddUndisclosedRecipients) { - const char* pBcc = fields->GetBcc(); //Do not free me! - if (pBcc && *pBcc) + bool hasBcc = false; + fields->HasHeader("Bcc", &hasBcc); + if (hasBcc) { nsCOMPtr stringService = mozilla::services::GetStringBundleService(); @@ -635,26 +435,32 @@ getter_Copies(undisclosedRecipients)); if (NS_SUCCEEDED(rv) && !undisclosedRecipients.IsEmpty()) { - PUSH_STRING("To: "); - PUSH_STRING(NS_LossyConvertUTF16toASCII(undisclosedRecipients).get()); - PUSH_STRING(":;"); - PUSH_NEWLINE (); - } + nsCOMPtr headerParser( + mozilla::services::GetHeaderParser()); + nsCOMPtr group; + headerParser->MakeGroupObject(undisclosedRecipients, + nullptr, 0, getter_AddRefs(group)); + recipients.AppendElement(group); + finalHeaders->SetAddressingHeader("To", recipients); + } } } } } } - if (pSubject && *pSubject) - ENCODE_AND_PUSH("Subject: ", false, pSubject, charset, usemime); + // We don't want to emit a Bcc header to the output. If we are saving this to + // Drafts/Sent, this is readded later in nsMsgSend.cpp. + finalHeaders->DeleteHeader("bcc"); // Skip no or empty priority. - if (pPriority && *pPriority) + nsAutoCString priority; + rv = fields->GetRawHeader("X-Priority", priority); + if (NS_SUCCEEDED(rv) && !priority.IsEmpty()) { nsMsgPriorityValue priorityValue; - NS_MsgGetPriorityFromString(pPriority, priorityValue); + NS_MsgGetPriorityFromString(priority.get(), priorityValue); // Skip default priority. if (priorityValue != nsMsgPriority::Default) { @@ -665,70 +471,47 @@ NS_MsgGetUntranslatedPriorityName(priorityValue, priorityName); // Output format: [X-Priority: ()] - PUSH_STRING("X-Priority: "); - PUSH_STRING(priorityValueString.get()); - PUSH_STRING(" ("); - PUSH_STRING(priorityName.get()); - PUSH_STRING(")"); - PUSH_NEWLINE(); + priorityValueString.AppendLiteral(" ("); + priorityValueString += priorityName; + priorityValueString.AppendLiteral(")"); + finalHeaders->SetRawHeader("X-Priority", priorityValueString, nullptr); } } - if (pReference && *pReference) { - PUSH_STRING ("References: "); - if (PL_strlen(pReference) >= 986) { - char *references = PL_strdup(pReference); - char *trimAt = PL_strchr(references+1, '<'); - char *ptr; - // per sfraser, RFC 1036 - a message header line should not exceed - // 998 characters including the header identifier - // retiring the earliest reference one after the other - // but keep the first one for proper threading - while (references && PL_strlen(references) >= 986 && trimAt) { - ptr = PL_strchr(trimAt+1, '<'); - if (ptr) - memmove(trimAt, ptr, PL_strlen(ptr)+1); // including the - else - break; - } - NS_ASSERTION(references, "null references"); - if (references) { - PUSH_STRING (references); - PR_Free(references); - } - } - else - PUSH_STRING (pReference); - PUSH_NEWLINE (); + nsAutoCString references; + finalHeaders->GetRawHeader("References", references); + if (!references.IsEmpty()) + { + // The References header should be kept under 998 characters: if it's too + // long, trim out the earliest references to make it smaller. + if (references.Length() > 986) { - const char *lastRef = PL_strrchr(pReference, '<'); - - if (lastRef) { - PUSH_STRING ("In-Reply-To: "); - PUSH_STRING (lastRef); - PUSH_NEWLINE (); + int32_t firstRef = references.FindChar('<'); + int32_t secondRef = references.FindChar('<', firstRef + 1); + if (secondRef > 0) + { + nsAutoCString newReferences(StringHead(references, secondRef)); + int32_t bracket = references.FindChar('<', + references.Length() + newReferences.Length() - 986); + if (bracket > 0) + { + newReferences.Append(Substring(references, bracket)); + finalHeaders->SetRawHeader("References", newReferences, nullptr); + } } } + // The In-Reply-To header is the last entry in the references header... + int32_t bracket = references.RFind("<"); + if (bracket >= 0) + finalHeaders->SetRawHeader("In-Reply-To", Substring(references, bracket), + nullptr); } - if (pOtherHdr && *pOtherHdr) { - /* Assume they already have the right newlines and continuations - and so on. for these headers, the PUSH_NEWLINE() happens in addressingWidgetOverlay.js */ - PUSH_STRING (pOtherHdr); - } - - if (buffer_tail > buffer + size - 1) { - PR_FREEIF(buffer); - return nullptr; - } - /* realloc it smaller... */ - char *newBuffer = (char*) PR_REALLOC(buffer, buffer_tail - buffer + 1); - if (!newBuffer) // The original bigger buffer is still usable to the caller. - return buffer; - - return newBuffer; + return NS_OK; } +#undef APPEND_BOOL // X-Mozilla-Draft-Info helper macro + static void GenerateGlobalRandomBytes(unsigned char *buf, int32_t len) { diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompUtils.h thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompUtils.h --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgCompUtils.h 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgCompUtils.h 2015-01-07 22:14:56.000000000 +0000 @@ -69,10 +69,9 @@ const char * /*organization*/, const char * /*other_random_headers*/); -char *mime_generate_headers (nsMsgCompFields *fields, - const char *charset, - nsMsgDeliverMode deliver_mode, - nsIPrompt * aPrompt, nsresult *status); +nsresult mime_generate_headers(nsIMsgCompFields *fields, + nsMsgDeliverMode deliver_mode, + msgIWritableStructuredHeaders *headers); char *mime_make_separator(const char *prefix); char *mime_gen_content_id(uint32_t aPartNum, const char *aEmailAddress); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgSend.cpp thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgSend.cpp --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgSend.cpp 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgSend.cpp 2015-01-07 22:14:56.000000000 +0000 @@ -64,6 +64,7 @@ #include "nsIMsgAccountManager.h" #include "nsNativeCharsetUtils.h" #include "nsIAbCard.h" +#include "nsIMsgAttachment.h" #include "nsIMsgProgress.h" #include "nsIMsgMessageService.h" #include "nsIMsgHdr.h" @@ -425,14 +426,9 @@ bool shouldDeleteDeliveryState = true; nsresult status; uint32_t i; - char *headers = 0; PRFileDesc *in_file = 0; - bool multipart_p = false; - bool plaintext_is_mainbody_p = false; // only using text converted from HTML? char *buffer = 0; - char *buffer_tail = 0; nsString msg; - bool tonews; bool body_is_us_ascii = true; nsMsgSendPart* toppart = nullptr; // The very top most container of the message @@ -465,18 +461,6 @@ bool maincontainerISrelatedpart = false; const char * toppart_type = nullptr; - // If we have any attachments, we generate multipart. - multipart_p = (m_attachment_count > 0); - - // to news is true if we have a m_field and we have a Newsgroup and it is not empty - tonews = false; - if (mCompFields) - { - const char* pstrzNewsgroup = mCompFields->GetNewsgroups(); - if (pstrzNewsgroup && *pstrzNewsgroup) - tonews = true; - } - status = m_status; if (NS_FAILED(status)) goto FAIL; @@ -571,8 +555,6 @@ if (! buffer) goto FAILMEM; - buffer_tail = buffer; - NS_ASSERTION (m_attachment_pending_count == 0, "m_attachment_pending_count != 0"); mComposeBundle->GetStringFromName(MOZ_UTF16("assemblingMessage"), @@ -743,8 +725,6 @@ /* Override attachment1_encoding here. */ PR_FREEIF(m_attachment1_encoding); m_attachment1_encoding = ToNewCString(m_plaintext->m_encoding); - - plaintext_is_mainbody_p = true; // converted plaintext is mainbody } } @@ -777,38 +757,35 @@ goto FAIL; } - /* Write out the message headers. - */ - headers = mime_generate_headers (mCompFields, mCompFields->GetCharacterSet(), - m_deliver_mode, promptObject, &status); - if (NS_FAILED(status)) - goto FAIL; + { + nsCOMPtr outputHeaders = + do_CreateInstance(NS_ISTRUCTUREDHEADERS_CONTRACTID); + status = mime_generate_headers(mCompFields, m_deliver_mode, outputHeaders); + if (NS_FAILED(status)) + goto FAIL; - if (!headers) - goto FAILMEM; + // Convert the blocks of headers into a single string for emission. + nsAutoCString headers; + outputHeaders->BuildMimeText(headers); + + // If we converted HTML into plaintext, the plaintext part (plainpart) + // already has its content-type and content-transfer-encoding ("other") + // headers set. + // + // In the specific case where such a plaintext part is the top-level message + // part (iff an HTML message is being sent as text only and no other + // attachments exist) we want to preserve the original plainpart headers, + // since they contain accurate transfer encoding and Mac type/creator + // information. + // + // So, in the above case we append the main message headers, otherwise we + // overwrite whatever headers may have existed. + if (plainpart && plainpart == toppart) + status = toppart->AppendOtherHeaders(headers.get()); + else + status = toppart->SetOtherHeaders(headers.get()); + } - // - // If we converted HTML into plaintext, the plaintext part (plainpart) - // already has its content-type and content-transfer-encoding - // ("other") headers set. - // - // In the specific case where such a plaintext part is the - // top level message part (iff an HTML message is being sent - // as text only and no other attachments exist) we want to - // preserve the original plainpart headers, since they - // contain accurate transfer encoding and Mac type/creator - // information. - // - // So, in the above case we append the main message headers, - // otherwise we overwrite whatever headers may have existed. - // - /* reordering of headers will happen in nsMsgSendPart::Write */ - if ((plainpart) && (plainpart == toppart)) - status = toppart->AppendOtherHeaders(headers); - else - status = toppart->SetOtherHeaders(headers); - PR_Free(headers); - headers = nullptr; if (NS_FAILED(status)) goto FAIL; @@ -876,7 +853,6 @@ buffer = mime_mailto_stream_read_buffer; if (! buffer) goto FAILMEM; - buffer_tail = buffer; // Gather all of the attachments for this message that are NOT // part of an enclosed MHTML message! @@ -1005,7 +981,6 @@ mainbody = nullptr; maincontainer = nullptr; - PR_FREEIF(headers); if (in_file) { PR_Close (in_file); @@ -2628,45 +2603,6 @@ return NS_OK; } -nsresult nsMsgComposeAndSend::SetMimeHeader(nsMsgCompFields::MsgHeaderID header, const char *value) -{ - char * dupHeader = nullptr; - nsresult ret = NS_ERROR_OUT_OF_MEMORY; - - switch (header) - { - case nsMsgCompFields::MSG_FROM_HEADER_ID : - case nsMsgCompFields::MSG_TO_HEADER_ID : - case nsMsgCompFields::MSG_REPLY_TO_HEADER_ID : - case nsMsgCompFields::MSG_CC_HEADER_ID : - case nsMsgCompFields::MSG_BCC_HEADER_ID : - dupHeader = mime_fix_addr_header(value); - break; - - case nsMsgCompFields::MSG_NEWSGROUPS_HEADER_ID : - case nsMsgCompFields::MSG_FOLLOWUP_TO_HEADER_ID : - dupHeader = mime_fix_news_header(value); - break; - - case nsMsgCompFields::MSG_FCC_HEADER_ID : - case nsMsgCompFields::MSG_ORGANIZATION_HEADER_ID : - case nsMsgCompFields::MSG_SUBJECT_HEADER_ID : - case nsMsgCompFields::MSG_REFERENCES_HEADER_ID : - case nsMsgCompFields::MSG_X_TEMPLATE_HEADER_ID : - dupHeader = mime_fix_header(value); - break; - - default : NS_ASSERTION(false, "invalid header"); // unhandled header - bad boy. - } - - if (dupHeader) - { - ret = mCompFields->SetAsciiHeader(header, dupHeader); - PR_Free(dupHeader); - } - return ret; -} - nsresult nsMsgComposeAndSend::InitCompositionFields(nsMsgCompFields *fields, const nsACString &aOriginalMsgURI, @@ -2690,21 +2626,6 @@ mCompFields->SetCharacterSet(fields->GetCharacterSet()); } - pStr = fields->GetMessageId(); - if (pStr) - { - mCompFields->SetMessageId((char *) pStr); - /* Don't bother checking for out of memory; if it fails, then we'll just - let the server generate the message-id, and suffer with the - possibility of duplicate messages.*/ - } - - pStr = fields->GetNewspostUrl(); - if (pStr && *pStr) - { - mCompFields->SetNewspostUrl((char *)pStr); - } - // Now, we will look for a URI defined as the default FCC pref. If this is set, // then SetFcc will use this value. The FCC field is a URI for the server that // will hold the "Sent" folder...the @@ -2741,7 +2662,7 @@ if (folder) { useDefaultFCC = false; - SetMimeHeader(nsMsgCompFields::MSG_FCC_HEADER_ID, fieldsFCC); + mCompFields->SetFcc(mime_fix_header(fieldsFCC)); } } } @@ -2835,20 +2756,9 @@ } } - mCompFields->SetNewspostUrl((char *) fields->GetNewspostUrl()); - - /* strip whitespace from and duplicate header fields. */ - SetMimeHeader(nsMsgCompFields::MSG_FROM_HEADER_ID, fields->GetFrom()); - SetMimeHeader(nsMsgCompFields::MSG_REPLY_TO_HEADER_ID, fields->GetReplyTo()); - SetMimeHeader(nsMsgCompFields::MSG_TO_HEADER_ID, fields->GetTo()); - SetMimeHeader(nsMsgCompFields::MSG_CC_HEADER_ID, fields->GetCc()); - SetMimeHeader(nsMsgCompFields::MSG_BCC_HEADER_ID, fields->GetBcc()); - SetMimeHeader(nsMsgCompFields::MSG_NEWSGROUPS_HEADER_ID, fields->GetNewsgroups()); - SetMimeHeader(nsMsgCompFields::MSG_FOLLOWUP_TO_HEADER_ID, fields->GetFollowupTo()); - SetMimeHeader(nsMsgCompFields::MSG_ORGANIZATION_HEADER_ID, fields->GetOrganization()); - SetMimeHeader(nsMsgCompFields::MSG_SUBJECT_HEADER_ID, fields->GetSubject()); - SetMimeHeader(nsMsgCompFields::MSG_REFERENCES_HEADER_ID, fields->GetReferences()); - SetMimeHeader(nsMsgCompFields::MSG_X_TEMPLATE_HEADER_ID, fields->GetTemplateName()); + // Copy the main bodies of headers over. + rv = mCompFields->AddAllHeaders(fields); + NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr srcAttachments; fields->GetAttachments(getter_AddRefs(srcAttachments)); @@ -2866,10 +2776,6 @@ } } - pStr = fields->GetOtherRandomHeaders(); - if (pStr) - mCompFields->SetOtherRandomHeaders((char *) pStr); - AddDefaultCustomHeaders(); AddMailFollowupToHeader(); @@ -2916,8 +2822,7 @@ mCompFields->GetBcc(), mCompFields->GetFcc(), mCompFields->GetNewsgroups(), mCompFields->GetFollowupTo(), mCompFields->GetSubject(), mCompFields->GetReferences(), - mCompFields->GetOrganization(), - mCompFields->GetOtherRandomHeaders()); + mCompFields->GetOrganization(), ""); } return NS_OK; } @@ -2936,9 +2841,6 @@ int32_t start = 0; int32_t end = 0; int32_t len = 0; - // preserve any custom headers that have been added through the UI - nsAutoCString newHeaderVal(mCompFields->GetOtherRandomHeaders()); - while (end != -1) { end = headersList.FindChar(',', start); if (end == -1) { @@ -2954,26 +2856,14 @@ nsCString headerVal; rv = mUserIdentity->GetCharAttribute(headerName.get(), headerVal); if (NS_SUCCEEDED(rv)) { - int32_t colonIdx = headerVal.FindChar(':') + 1; - if (colonIdx != 0) { // check that the header is *most likely* valid. - char * convHeader = - nsMsgI18NEncodeMimePartIIStr(headerVal.get() + colonIdx, - false, - mCompFields->GetCharacterSet(), - colonIdx, - true); - if (convHeader) { - newHeaderVal.Append(Substring(headerVal, 0, colonIdx)); - newHeaderVal.Append(convHeader); - // we must terminate the header with CRLF here - // as nsMsgCompUtils.cpp just calls PUSH_STRING - newHeaderVal.Append("\r\n"); - PR_Free(convHeader); - } + int32_t colonIdx = headerVal.FindChar(':'); + if (colonIdx > 0) { // check that the header is *most likely* valid. + nsCString name(Substring(headerVal, 0, colonIdx)); + mCompFields->SetRawHeader(name.get(), + Substring(headerVal, colonIdx + 1), nullptr); } } } - mCompFields->SetOtherRandomHeaders(newHeaderVal.get()); } return rv; } @@ -2984,13 +2874,13 @@ nsMsgComposeAndSend::AddMailFollowupToHeader() { nsresult rv; - // Get OtherRandomHeaders... - nsDependentCString customHeaders(mCompFields->GetOtherRandomHeaders()); - // ...and look for MFT-Header. Stop here if MFT is already set. - NS_NAMED_LITERAL_CSTRING(mftHeaderLabel, "Mail-Followup-To: "); - if (StringBeginsWith(customHeaders, mftHeaderLabel) || - customHeaders.Find("\r\nMail-Followup-To: ") != -1) + // If there's already a Mail-Followup-To header, don't need to do anything. + nsAutoCString mftHeader; + mCompFields->GetRawHeader(HEADER_MAIL_FOLLOWUP_TO, mftHeader); + if (!mftHeader.IsEmpty()) + { return NS_OK; + } // Get list of subscribed mailing lists nsAutoCString mailing_lists; @@ -3034,17 +2924,8 @@ return NS_OK; // Set Mail-Followup-To - char * mimeHeader = nsMsgI18NEncodeMimePartIIStr(recipients.get(), true, - mCompFields->GetCharacterSet(), mftHeaderLabel.Length(), true); - if (!mimeHeader) - return NS_ERROR_FAILURE; - - customHeaders.Append(mftHeaderLabel); - customHeaders.Append(mimeHeader); - customHeaders.AppendLiteral("\r\n"); - mCompFields->SetOtherRandomHeaders(customHeaders.get()); - PR_Free(mimeHeader); - return NS_OK; + return mCompFields->SetRawHeader(HEADER_MAIL_FOLLOWUP_TO, recipients, + mCompFields->GetCharacterSet()); } // Add Mail-Reply-To header @@ -3053,14 +2934,10 @@ nsMsgComposeAndSend::AddMailReplyToHeader() { nsresult rv; - // Get OtherRandomHeaders... - nsDependentCString customHeaders(mCompFields->GetOtherRandomHeaders()); - // ...and look for MRT-Header. Stop here if MRT is already set. - NS_NAMED_LITERAL_CSTRING(mrtHeaderLabel, "Mail-Reply-To: "); - nsAutoCString headers_match = nsAutoCString("\r\n"); - headers_match.Append(mrtHeaderLabel); - if ((StringHead(customHeaders, mrtHeaderLabel.Length()) == mrtHeaderLabel) || - (customHeaders.Find(headers_match) != -1)) + // If there's already a Mail-Reply-To header, don't need to do anything. + nsAutoCString mrtHeader; + mCompFields->GetRawHeader(HEADER_MAIL_REPLY_TO, mrtHeader); + if (!mrtHeader.IsEmpty()) return NS_OK; // Get list of reply-to mangling mailing lists @@ -3119,27 +2996,16 @@ mailReplyTo = mCompFields->GetFrom(); else mailReplyTo = replyTo; - char * mimeHeader = nsMsgI18NEncodeMimePartIIStr(mailReplyTo.get(), true, - mCompFields->GetCharacterSet(), mrtHeaderLabel.Length(), true); - if (!mimeHeader) - return NS_ERROR_FAILURE; - customHeaders.Append(mrtHeaderLabel); - customHeaders.Append(mimeHeader); - customHeaders.AppendLiteral("\r\n"); - mCompFields->SetOtherRandomHeaders(customHeaders.get()); - PR_Free(mimeHeader); + mCompFields->SetRawHeader(HEADER_MAIL_REPLY_TO, mailReplyTo, + mCompFields->GetCharacterSet()); return NS_OK; } nsresult nsMsgComposeAndSend::AddXForwardedMessageIdHeader() { - nsAutoCString otherHeaders; - otherHeaders.Append(nsDependentCString(mCompFields->GetOtherRandomHeaders())); - otherHeaders.Append(NS_LITERAL_CSTRING("X-Forwarded-Message-Id: ")); - otherHeaders.Append(nsDependentCString(mCompFields->GetReferences())); - otherHeaders.Append(NS_LITERAL_CSTRING("\r\n")); - return mCompFields->SetOtherRandomHeaders(otherHeaders.get()); + return mCompFields->SetRawHeader("X-Forwarded-Message-Id", + nsDependentCString(mCompFields->GetReferences()), nullptr); } nsresult diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgSend.h thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgSend.h --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/src/nsMsgSend.h 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/src/nsMsgSend.h 2015-01-07 22:14:56.000000000 +0000 @@ -233,7 +233,6 @@ const nsACString &aOriginalMsgURI, MSG_ComposeType aType); - nsresult SetMimeHeader(nsMsgCompFields::MsgHeaderID header, const char *value); NS_IMETHOD GetBodyFromEditor(); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/test_expandMailingLists.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/test_expandMailingLists.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/test_expandMailingLists.js 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/test_expandMailingLists.js 2015-01-07 22:14:56.000000000 +0000 @@ -54,10 +54,10 @@ MailServices.ab.directories; // Test expansion of list with no description. - checkPopulate("simpson ", "Simpson ,Marge ,Bart ,\"lisa@example.com\" "); + checkPopulate("simpson ", "Simpson , Marge , Bart , \"lisa@example.com\" "); // Test expansion fo list with description. - checkPopulate("marge ", "Simpson ,Marge "); + checkPopulate("marge ", "Simpson , Marge "); // Test we don't mistake an email address for a list, with a few variations. checkPopulate("Simpson ", "Simpson "); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/test_messageHeaders.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/test_messageHeaders.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/test_messageHeaders.js 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/test_messageHeaders.js 2015-01-07 22:14:56.000000000 +0000 @@ -200,7 +200,7 @@ getBasicSmtpServer()); fields.priority = "high"; fields.references = " "; - fields.otherRandomHeaders = "X-Fake-Header: 124\r\n"; + fields.setHeader("X-Fake-Header", "124"); let before = Date.now(); let msgHdr = yield richCreateMessage(fields, [], identity); let after = Date.now(); @@ -268,7 +268,7 @@ yield richCreateMessage(fields, [], identity); checkDraftHeaders({ // The identity should override the compose fields here. - "Newsgroups": "mozilla.test,mozilla.test.multimedia", + "Newsgroups": "mozilla.test, mozilla.test.multimedia", "Followup-To": "mozilla.test", "X-Mozilla-News-Host": "localhost", }); @@ -496,7 +496,7 @@ }); yield sendMessage({"bcc": "Somebody ", diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/test_nsIMsgCompFields.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/test_nsIMsgCompFields.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/test_nsIMsgCompFields.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/test_nsIMsgCompFields.js 2015-01-07 22:14:56.000000000 +0000 @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Test that nsIMsgCompFields works properly + +let Ci = Components.interfaces; + +let nsMsgCompFields = Components.Constructor( + "@mozilla.org/messengercompose/composefields;1", + Ci.nsIMsgCompFields); + +function check_headers(enumerator, container) { + let checkValues = new Set([for (header of container) header.toLowerCase()]); + while (enumerator.hasMore()) { + let value = enumerator.getNext().toLowerCase(); + do_check_true(checkValues.has(value)); + checkValues.delete(value); + } + do_check_eq(checkValues.size, 0); +} + +function run_test() { + let fields = new nsMsgCompFields; + do_check_true(fields instanceof Ci.nsIMsgCompFields); + do_check_true(fields instanceof Ci.msgIStructuredHeaders); + do_check_true(fields instanceof Ci.msgIWritableStructuredHeaders); + check_headers(fields.headerNames, []); + do_check_false(fields.hasRecipients); + + // Try some basic headers + fields.setHeader("From", [{name: "", email: "a@test.invalid"}]); + let from = fields.getHeader("from"); + do_check_eq(from.length, 1); + do_check_eq(from[0].email, "a@test.invalid"); + check_headers(fields.headerNames, ["From"]); + do_check_false(fields.hasRecipients); + + // Add a To header + fields.setHeader("To", [{name: "", email: "b@test.invalid"}]); + check_headers(fields.headerNames, ["From", "To"]); + do_check_true(fields.hasRecipients); + + // Delete a header... + fields.deleteHeader("from"); + do_check_eq(fields.getHeader("From"), undefined); + check_headers(fields.headerNames, ["To"]); + + // Subject should work and not convert to RFC 2047. + fields.subject = "\u79c1\u306f\u4ef6\u540d\u5348\u524d"; + do_check_eq(fields.subject, "\u79c1\u306f\u4ef6\u540d\u5348\u524d"); + do_check_eq(fields.getHeader("Subject"), + "\u79c1\u306f\u4ef6\u540d\u5348\u524d"); + + // Check header synchronization. + fields.from = "a@test.invalid"; + do_check_eq(fields.from, "a@test.invalid"); + do_check_eq(fields.getHeader("From")[0].email, "a@test.invalid"); + fields.from = null; + do_check_eq(fields.getHeader("From"), undefined); +} diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/test_nsMsgCompose1.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/test_nsMsgCompose1.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/test_nsMsgCompose1.js 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/test_nsMsgCompose1.js 2015-01-07 22:14:56.000000000 +0000 @@ -38,7 +38,13 @@ msgCompose.initialize(params); msgCompose.expandMailingLists(); - do_check_eq(fields.to, aCheckTo); + let addresses = fields.getHeader("To"); + let checkEmails = MailServices.headerParser.parseDecodedHeader(aCheckTo); + do_check_eq(addresses.length, checkEmails.length); + for (let i = 0; i < addresses.length; i++) { + do_check_eq(addresses[i].name, checkEmails[i].name); + do_check_eq(addresses[i].email, checkEmails[i].email); + } } function run_test() { diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/xpcshell.ini thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/xpcshell.ini --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/compose/test/unit/xpcshell.ini 2015-01-06 13:13:44.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/compose/test/unit/xpcshell.ini 2015-01-07 22:14:56.000000000 +0000 @@ -12,6 +12,7 @@ [test_expandMailingLists.js] [test_mailtoURL.js] [test_messageHeaders.js] +[test_nsIMsgCompFields.js] [test_nsMsgCompose1.js] [test_nsMsgCompose2.js] [test_nsMsgCompose3.js] diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/public/msgIStructuredHeaders.idl thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/public/msgIStructuredHeaders.idl --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/public/msgIStructuredHeaders.idl 2015-01-06 13:13:45.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/public/msgIStructuredHeaders.idl 2015-01-07 22:14:57.000000000 +0000 @@ -35,7 +35,7 @@ * exception if the header is missing but rather return an appropriate default * value as indicated in their documentation. */ -[scriptable, uuid(6c105b83-fb73-4282-bd00-05eaf0f4da1d)] +[scriptable, uuid(e109bf4f-788f-47ba-bfa8-1236ede05597)] interface msgIStructuredHeaders : nsISupports { /** * Retrieve the value of the header stored in this set of headers. If the @@ -97,6 +97,19 @@ */ readonly attribute nsIUTF8StringEnumerator headerNames; + /** + * Retrieve the MIME representation of all of the headers. + * + * The header values are emitted in an ASCII form, unless internationalized + * email addresses are involved. The extra CRLF indicating the end of headers + * is not included in this representation. + * + * This accessor is provided mainly for the benefit of C++ consumers of this + * interface, since the JSMime headeremitter functionality allows more + * fine-grained customization of the results. + */ + AUTF8String buildMimeText(); + %{C++ /** * A special variant of getAddressingHeader that is specialized better for C++ @@ -122,7 +135,7 @@ * An interface that enhances msgIStructuredHeaders by allowing the values of * headers to be modified. */ -[scriptable, uuid(de109a13-47e5-4b69-8784-854b66a88da6)] +[scriptable, uuid(5dcbbef6-2356-45d8-86d7-b3e73f9c9a0c)] interface msgIWritableStructuredHeaders : msgIStructuredHeaders { /** * Store the given value for the given header, overwriting any previous value diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/public/nsIMimeHeaders.idl thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/public/nsIMimeHeaders.idl --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/public/nsIMimeHeaders.idl 2015-01-06 13:13:45.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/public/nsIMimeHeaders.idl 2015-01-07 22:14:57.000000000 +0000 @@ -13,7 +13,7 @@ /** * An interface that can extract individual headers from a body of headers. */ -[scriptable, uuid(8a570955-c641-4471-8cc1-1bc1bca65561)] +[scriptable, uuid(a9222679-b991-4786-8314-f8819c3a2ba3)] interface nsIMimeHeaders : msgIStructuredHeaders { /// Feed in the text of headers void initialize(in ACString allHeaders); @@ -29,6 +29,13 @@ */ ACString extractHeader(in string headerName, in boolean getAllOfThem); - /// The current text of all header data. + /** + * The current text of all header data. + * + * Unlike the asMimeText property, this result preserves the original + * representation of the header text, including alternative line endings or + * custom, non-8-bit text. For instances of this interface, this attribute is + * usually preferable to asMimeText. + */ readonly attribute ACString allHeaders; }; diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/extraMimeParsers.jsm thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/extraMimeParsers.jsm --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/extraMimeParsers.jsm 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/extraMimeParsers.jsm 2015-01-07 22:14:57.000000000 +0000 @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function parseNewsgroups(headers) { + let ng = []; + for (let header of headers) { + ng = ng.concat(header.split(/\s*,\s*/)); + } + return ng; +} + +function emitNewsgroups(groups) { + // Don't encode the newsgroups names in RFC 2047... + if (groups.length == 1) + this.addText(groups[0], false); + else { + this.addText(groups[0], false); + for (let i = 1; i < groups.length; i++) { + this.addText(", ", true); + this.addText(groups[i], false); + } + } +} + +jsmime.headerparser.addStructuredDecoder("Newsgroups", parseNewsgroups); +jsmime.headerparser.addStructuredDecoder("Followup-To", parseNewsgroups); +jsmime.headeremitter.addStructuredEncoder("Newsgroups", emitNewsgroups); +jsmime.headeremitter.addStructuredEncoder("Followup-To", emitNewsgroups); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/mimedrft.cpp thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/mimedrft.cpp --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/mimedrft.cpp 2015-01-06 13:13:45.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/mimedrft.cpp 2015-01-07 22:14:57.000000000 +0000 @@ -33,6 +33,7 @@ #include "nsIIOService.h" #include "nsNetUtil.h" #include "comi18n.h" +#include "nsIMsgAttachment.h" #include "nsIMsgCompFields.h" #include "nsMsgCompCID.h" #include "nsIMsgComposeService.h" @@ -326,7 +327,6 @@ const char *organization, const char *subject, const char *references, - const char *other_random_headers, const char *priority, const char *newspost_url, char *charset, @@ -404,11 +404,6 @@ cFields->SetReferences(!val.IsEmpty() ? val.get() : references); } - if (other_random_headers) { - MIME_DecodeMimeHeader(other_random_headers, charset, false, true, val); - cFields->SetOtherRandomHeaders(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : other_random_headers)); - } - if (priority) { MIME_DecodeMimeHeader(priority, charset, false, true, val); nsMsgPriorityValue priorityValue; @@ -1242,7 +1237,7 @@ CreateCompositionFields( from, repl, to, cc, bcc, fcc, grps, foll, - org, subj, refs, 0, priority, news_host, + org, subj, refs, priority, news_host, mdd->mailcharset, getter_AddRefs(fields)); @@ -1528,7 +1523,7 @@ else { CreateCompositionFields( from, repl, to, cc, bcc, fcc, grps, foll, - org, subj, refs, 0, priority, news_host, + org, subj, refs, priority, news_host, mdd->mailcharset, getter_AddRefs(fields)); if (fields) diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/mimeJSComponents.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/mimeJSComponents.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/mimeJSComponents.js 2015-01-06 13:13:45.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/mimeJSComponents.js 2015-01-07 22:14:57.000000000 +0000 @@ -40,6 +40,25 @@ }; /** + * If we get XPConnect-wrapped objects for msgIAddressObjects, we will have + * properties defined for 'group' that throws off jsmime. This function converts + * the addresses into the form that jsmime expects. + */ +function fixXpconnectAddresses(addrs) { + return addrs.map((addr) => { + // This is ideally !addr.group, but that causes a JS strict warning, if + // group is not in addr, since that's enabled in all chrome code now. + if (!('group' in addr) || addr.group === undefined || addr.group === null) { + return MimeAddressParser.prototype.makeMailboxObject(addr.name, + addr.email); + } else { + return MimeAddressParser.prototype.makeGroupObject(addr.name, + fixXpconnectAddresses(addr.group)); + } + }); +} + +/** * This is a base handler for supporting msgIStructuredHeaders, since we have * two implementations that need the readable aspects of the interface. */ @@ -88,7 +107,22 @@ get headerNames() { return new StringEnumerator(this._headers.keys()); - } + }, + + buildMimeText: function () { + if (this._headers.size == 0) { + return ""; + } + let handler = new HeaderHandler(); + let emitter = jsmime.headeremitter.makeStreamingEmitter(handler, { + useASCII: true + }); + for (let [value, header] of this._headers) { + emitter.addStructuredHeader(value, header); + } + emitter.finish(); + return handler.value; + }, }; @@ -156,7 +190,7 @@ }, setAddressingHeader: function (aHeaderName, aAddresses, aCount) { - this.setHeader(aHeaderName, aAddresses); + this.setHeader(aHeaderName, fixXpconnectAddresses(aAddresses)); }, setRawHeader: function (aHeaderName, aValue, aCharset) { @@ -237,6 +271,7 @@ }, makeMimeHeader: function (addresses, length) { + addresses = fixXpconnectAddresses(addresses); // Don't output any necessary continuations, so make line length as large as // possible first. let options = { @@ -307,7 +342,7 @@ makeGroupObject: function (aName, aMembers) { let object = Object.create(EmailGroup); object.name = aName; - object.members = aMembers; + object.group = aMembers; return object; }, @@ -422,7 +457,7 @@ if (aStructured) { // Structured really means "this is an addressing header" let addresses = MimeParser.parseHeaderField(aHeader, - MimeParser.HEADER_ADDRESS); + MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_DECODE_2047); // This happens in one of our tests if there is a "bare" email but no // @ sign. Without it, the result disappears since our emission code // assumes that an empty email is not worth emitting. diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/moz.build thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/moz.build --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/moz.build 2015-01-06 13:13:45.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/moz.build 2015-01-07 22:14:57.000000000 +0000 @@ -81,6 +81,7 @@ ] EXTRA_JS_MODULES += [ + 'extraMimeParsers.jsm', 'jsmime.jsm', 'mimeParser.jsm' ] diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/msgMime.manifest thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/msgMime.manifest --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/src/msgMime.manifest 2015-01-06 13:13:45.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/src/msgMime.manifest 2015-01-07 22:14:57.000000000 +0000 @@ -6,3 +6,4 @@ contract @mozilla.org/messenger/mimeconverter;1 {93f8c049-80ed-4dda-9000-94ad8daba44c} contract @mozilla.org/messenger/headerparser;1 {96bd8769-2d0e-4440-963d-22b97fb3ba77} contract @mozilla.org/messenger/structuredheaders;1 {c560806a-425f-4f0f-bf69-397c58c599a7} +category custom-mime-encoder A-extra resource:///modules/extraMimeParsers.jsm diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/test/unit/test_structured_headers.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/test/unit/test_structured_headers.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mailnews/mime/test/unit/test_structured_headers.js 2015-01-06 13:13:45.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mailnews/mime/test/unit/test_structured_headers.js 2015-01-07 22:14:57.000000000 +0000 @@ -162,6 +162,30 @@ } }); +add_task(function* checkBuildMimeText() { + let headers = new StructuredHeaders(); + headers.setHeader("To", [{name: "François Smith", email: "user@☃.invalid"}]); + headers.setHeader("From", [{name: "John Doe", email: "jdoe@test.invalid"}]); + headers.setHeader("Subject", "A subject that spans a distance quite in " + + "excess of 80 characters so as to force an intermediary CRLF"); + let mimeText = + "To: =?UTF-8?Q?Fran=c3=a7ois_Smith?= \r\n" + + "From: John Doe \r\n" + + "Subject: A subject that spans a distance quite in excess of 80 characters so\r\n" + + " as to force an intermediary CRLF\r\n"; + do_check_eq(headers.buildMimeText(), mimeText); + + // Check the version used for the nsIMimeHeaders implementation. This requires + // initializing with a UTF-8 version. + let utf8Text = mimeText.replace("☃", "\xe2\x98\x83"); + let mimeHeaders = Cc["@mozilla.org/messenger/mimeheaders;1"] + .createInstance(Ci.nsIMimeHeaders); + mimeHeaders.initialize(utf8Text); + do_check_eq(mimeHeaders.getHeader("To")[0].email, "user@☃.invalid"); + do_check_eq(mimeHeaders.buildMimeText(), mimeText); + do_check_eq(mimeHeaders.allHeaders, utf8Text); +}); + function run_test() { run_next_test(); } diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/accessible/base/nsAccessibilityService.h thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/accessible/base/nsAccessibilityService.h --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/accessible/base/nsAccessibilityService.h 2015-01-06 13:14:16.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/accessible/base/nsAccessibilityService.h 2015-01-07 22:15:18.000000000 +0000 @@ -251,6 +251,8 @@ inline bool IPCAccessibilityActive() { + // XXX temporarily disable ipc accessibility because of crashes. +return false; #ifdef MOZ_B2G return false; #else diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/app/b2g.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/app/b2g.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/app/b2g.js 2015-01-06 13:14:17.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/app/b2g.js 2015-01-07 22:15:18.000000000 +0000 @@ -161,16 +161,8 @@ pref("browser.ssl_override_behavior", 2); pref("browser.xul.error_pages.expert_bad_cert", false); -// disable logging for the search service by default -pref("browser.search.log", false); - // disable updating pref("browser.search.update", false); -pref("browser.search.update.log", false); -pref("browser.search.updateinterval", 6); - -// enable search suggestions by default -pref("browser.search.suggest.enabled", true); // tell the search service that we don't really expose the "current engine" pref("browser.search.noCurrentEngine", true); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/dolphin/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/dolphin/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/dolphin/sources.xml 2015-01-06 13:14:17.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/dolphin/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/emulator/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/emulator/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/emulator/sources.xml 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/emulator/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -19,13 +19,13 @@ - + - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/emulator-ics/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/emulator-ics/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/emulator-ics/sources.xml 2015-01-06 13:14:17.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/emulator-ics/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -19,13 +19,13 @@ - + - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/emulator-jb/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/emulator-jb/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/emulator-jb/sources.xml 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/emulator-jb/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -17,10 +17,10 @@ - + - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/emulator-kk/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/emulator-kk/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/emulator-kk/sources.xml 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/emulator-kk/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/flame/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/flame/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/flame/sources.xml 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/flame/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -17,10 +17,10 @@ - + - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/flame-kk/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/flame-kk/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/flame-kk/sources.xml 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/flame-kk/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/gaia.json thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/gaia.json --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/gaia.json 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/gaia.json 2015-01-07 22:15:18.000000000 +0000 @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "c175a29477e68e70142687587844423a75dcf4cc", + "revision": "6505a7b6b4c727a9b561c1220b4ecf726248c5c5", "repo_path": "integration/gaia-central" } diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/hamachi/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/hamachi/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/hamachi/sources.xml 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/hamachi/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -17,11 +17,11 @@ - + - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/helix/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/helix/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/helix/sources.xml 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/helix/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -15,7 +15,7 @@ - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/nexus-4/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/nexus-4/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/nexus-4/sources.xml 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/nexus-4/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -17,10 +17,10 @@ - + - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/wasabi/sources.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/wasabi/sources.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/b2g/config/wasabi/sources.xml 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/b2g/config/wasabi/sources.xml 2015-01-07 22:15:18.000000000 +0000 @@ -17,12 +17,12 @@ - + - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/app/profile/firefox.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/app/profile/firefox.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/app/profile/firefox.js 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/app/profile/firefox.js 2015-01-07 22:15:18.000000000 +0000 @@ -396,9 +396,6 @@ // pointer to the default engine name pref("browser.search.defaultenginename", "chrome://browser-region/locale/region.properties"); -// disable logging for the search service by default -pref("browser.search.log", false); - // Ordering of Search Engines in the Engine list. pref("browser.search.order.1", "chrome://browser-region/locale/region.properties"); pref("browser.search.order.2", "chrome://browser-region/locale/region.properties"); @@ -417,34 +414,11 @@ // context menu searches open in the foreground pref("browser.search.context.loadInBackground", false); -// send ping to the server to update -pref("browser.search.update", true); - -// disable logging for the search service update system by default -pref("browser.search.update.log", false); - -// Check whether we need to perform engine updates every 6 hours -pref("browser.search.update.interval", 21600); - -// enable search suggestions by default -pref("browser.search.suggest.enabled", true); - pref("browser.search.showOneOffButtons", true); -#ifdef MOZ_OFFICIAL_BRANDING -// {moz:official} expands to "official" -pref("browser.search.official", true); -#endif - // How many times to show the new search highlight pref("browser.search.highlightCount", 5); -// geoip end-point and timeout -pref("browser.search.geoip.url", "https://location.services.mozilla.com/v1/country?key=%MOZILLA_API_KEY%"); -// NOTE: this timeout figure is also the "high" value for the telemetry probe -// SEARCH_SERVICE_COUNTRY_FETCH_MS - if you change this also change that probe. -pref("browser.search.geoip.timeout", 2000); - pref("browser.sessionhistory.max_entries", 50); // handle links targeting new windows @@ -1803,7 +1777,7 @@ #endif #ifdef NIGHTLY_BUILD -pref("browser.tabs.remote.autostart.1", true); +pref("browser.tabs.remote.autostart.1", false); #endif // Temporary pref to allow printing in e10s windows on some platforms. @@ -1816,6 +1790,7 @@ #ifdef NIGHTLY_BUILD // Enable e10s add-on interposition by default. pref("extensions.interposition.enabled", true); +pref("extensions.interposition.prefetching", true); #endif pref("browser.defaultbrowser.notificationbar", false); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/browser.css thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/browser.css --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/browser.css 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/browser.css 2015-01-07 22:15:18.000000000 +0000 @@ -418,16 +418,16 @@ /* For results that are actions, their description text is shown instead of the URL - this needs to follow the locale's direction, unlike URLs. */ -panel:not([noactions]) > richlistbox > richlistitem[type~="action"]:-moz-locale-dir(rtl) > .ac-url-box { +panel:not([noactions]) > richlistbox > richlistitem.overridable-action:-moz-locale-dir(rtl) > .ac-url-box { direction: rtl; } -panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-url > .ac-action-text, -panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-action-icon { +panel[noactions] > richlistbox > richlistitem.overridable-action > .ac-url-box > .ac-url > .ac-action-text, +panel[noactions] > richlistbox > richlistitem.overridable-action > .ac-url-box > .ac-action-icon { visibility: collapse; } -panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-url > .ac-url-text { +panel[noactions] > richlistbox > richlistitem.overridable-action > .ac-url-box > .ac-url > .ac-url-text { visibility: visible; } diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/browser-fxaccounts.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/browser-fxaccounts.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/browser-fxaccounts.js 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/browser-fxaccounts.js 2015-01-07 22:15:18.000000000 +0000 @@ -274,11 +274,22 @@ let note = null; switch (this._migrationInfo.state) { case this.fxaMigrator.STATE_USER_FXA: { - let msg = this.strings.GetStringFromName("needUserLong"); - let upgradeLabel = - this.strings.GetStringFromName("upgradeToFxA.label"); - let upgradeAccessKey = - this.strings.GetStringFromName("upgradeToFxA.accessKey"); + // There are 2 cases here - no email address means it is an offer on + // the first device (so the user is prompted to create an account). + // If there is an email address it is the "join the party" flow, so the + // user is prompted to sign in with the address they previously used. + let msg, upgradeLabel, upgradeAccessKey; + if (this._migrationInfo.email) { + msg = this.strings.formatStringFromName("signInAfterUpgradeOnOtherDevice.description", + [this._migrationInfo.email], + 1); + upgradeLabel = this.strings.GetStringFromName("signInAfterUpgradeOnOtherDevice.label"); + upgradeAccessKey = this.strings.GetStringFromName("signInAfterUpgradeOnOtherDevice.accessKey"); + } else { + msg = this.strings.GetStringFromName("needUserLong"); + upgradeLabel = this.strings.GetStringFromName("upgradeToFxA.label"); + upgradeAccessKey = this.strings.GetStringFromName("upgradeToFxA.accessKey"); + } note = new Weave.Notification( undefined, msg, undefined, Weave.Notifications.PRIORITY_WARNING, [ new Weave.NotificationButton(upgradeLabel, upgradeAccessKey, () => { @@ -321,11 +332,9 @@ this.openSignInAgainPage("menupanel"); break; case "migrate-signup": - this.fxaMigrator.createFxAccount(window); - break; case "migrate-verify": - // Instead of using the migrator module directly here the UX calls for - // us to open prefs which has a "resend" button. + // The migration flow calls for the menu item to open sync prefs rather + // than requesting migration start immediately. this.openPreferences(); break; default: diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/browser.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/browser.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/browser.js 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/browser.js 2015-01-07 22:15:18.000000000 +0000 @@ -2082,7 +2082,7 @@ let engine = Services.search.getEngineByAlias(keyword); if (engine) { - let submission = engine.getSubmission(param); + let submission = engine.getSubmission(param, null, "keyword"); postData = submission.postData; aCallback({ postData: submission.postData, url: submission.uri.spec, mayInheritPrincipal: mayInheritPrincipal }); @@ -3568,6 +3568,11 @@ let count = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS"); count.add(countId); }, + + recordOneoffSearchInTelemetry: function (engine, source, type, where) { + let id = this._getSearchEngineId(engine) + "." + source; + BrowserUITelemetry.countOneoffSearchEvent(id, type, where); + } }; const SearchHighlight = { diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/content-UITour.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/content-UITour.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/content-UITour.js 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/content-UITour.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,91 +0,0 @@ -let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -const PREF_TEST_WHITELIST = "browser.uitour.testingOrigins"; -const UITOUR_PERMISSION = "uitour"; - -let UITourListener = { - handleEvent: function (event) { - if (!Services.prefs.getBoolPref("browser.uitour.enabled")) { - return; - } - if (!this.ensureTrustedOrigin()) { - return; - } - addMessageListener("UITour:SendPageCallback", this); - addMessageListener("UITour:SendPageNotification", this); - sendAsyncMessage("UITour:onPageEvent", {detail: event.detail, type: event.type}); - }, - - isTestingOrigin: function(aURI) { - if (Services.prefs.getPrefType(PREF_TEST_WHITELIST) != Services.prefs.PREF_STRING) { - return false; - } - - // Add any testing origins (comma-seperated) to the whitelist for the session. - for (let origin of Services.prefs.getCharPref(PREF_TEST_WHITELIST).split(",")) { - try { - let testingURI = Services.io.newURI(origin, null, null); - if (aURI.prePath == testingURI.prePath) { - return true; - } - } catch (ex) { - Cu.reportError(ex); - } - } - return false; - }, - - // This function is copied from UITour.jsm. - isSafeScheme: function(aURI) { - let allowedSchemes = new Set(["https", "about"]); - if (!Services.prefs.getBoolPref("browser.uitour.requireSecure")) - allowedSchemes.add("http"); - - if (!allowedSchemes.has(aURI.scheme)) - return false; - - return true; - }, - - ensureTrustedOrigin: function() { - if (content.top != content) - return false; - - let uri = content.document.documentURIObject; - - if (uri.schemeIs("chrome")) - return true; - - if (!this.isSafeScheme(uri)) - return false; - - let permission = Services.perms.testPermission(uri, UITOUR_PERMISSION); - if (permission == Services.perms.ALLOW_ACTION) - return true; - - return this.isTestingOrigin(uri); - }, - - receiveMessage: function(aMessage) { - switch (aMessage.name) { - case "UITour:SendPageCallback": - this.sendPageEvent("Response", aMessage.data); - break; - case "UITour:SendPageNotification": - this.sendPageEvent("Notification", aMessage.data); - break; - } - }, - - sendPageEvent: function (type, detail) { - let doc = content.document; - let eventName = "mozUITour" + type; - let event = new doc.defaultView.CustomEvent(eventName, { - bubbles: true, - detail: Cu.cloneInto(detail, doc.defaultView) - }); - doc.dispatchEvent(event); - } -}; - -addEventListener("mozUITour", UITourListener, false, true); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/test/general/browser_aboutHome.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/test/general/browser_aboutHome.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/test/general/browser_aboutHome.js 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/test/general/browser_aboutHome.js 2015-01-07 22:15:18.000000000 +0000 @@ -439,7 +439,7 @@ info("Waiting for popup to open"); EventUtils.synthesizeMouseAtCenter(searchIcon, {}, gBrowser.selectedBrowser.contentWindow); yield promiseWaitForEvent(panel, "popupshown"); - ok("Saw popup open"); + info("Saw popup open"); let promise = promisePrefsOpen(); let item = window.document.getElementById("abouthome-search-panel-manage"); @@ -632,12 +632,33 @@ } let promisePrefsOpen = Task.async(function*() { - info("Waiting for the preferences tab to open..."); - let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true); - let tab = event.target; - yield promiseTabLoadEvent(tab); - is(tab.linkedBrowser.currentURI.spec, "about:preferences#search", "Should have seen the prefs tab"); - gBrowser.removeTab(tab); + if (Services.prefs.getBoolPref("browser.preferences.inContent")) { + info("Waiting for the preferences tab to open..."); + let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true); + let tab = event.target; + yield promiseTabLoadEvent(tab); + is(tab.linkedBrowser.currentURI.spec, "about:preferences#search", "Should have seen the prefs tab"); + gBrowser.removeTab(tab); + } else { + info("Waiting for the preferences window to open..."); + yield new Promise(resolve => { + let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher); + winWatcher.registerNotification(function onWin(subj, topic, data) { + if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) { + subj.addEventListener("load", function onLoad() { + subj.removeEventListener("load", onLoad); + is(subj.document.documentURI, "chrome://browser/content/preferences/preferences.xul", "Should have seen the prefs window"); + winWatcher.unregisterNotification(onWin); + executeSoon(() => { + subj.close(); + resolve(); + }); + }); + } + }); + }); + } }); function promiseNewEngine(basename) { diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/test/general/browser_action_keyword_override.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/test/general/browser_action_keyword_override.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/test/general/browser_action_keyword_override.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/test/general/browser_action_keyword_override.js 2015-01-07 22:15:18.000000000 +0000 @@ -0,0 +1,35 @@ +add_task(function*() { + // This test is only relevant if UnifiedComplete is enabled. + if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) { + todo(false, "Stop supporting old autocomplete components."); + return; + } + + let itemId = + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + NetUtil.newURI("http://example.com/?q=%s"), + PlacesUtils.bookmarks.DEFAULT_INDEX, + "test"); + PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword"); + + registerCleanupFunction(() => { + PlacesUtils.bookmarks.removeItem(itemId); + }); + + yield promiseAutocompleteResultPopup("keyword search"); + let result = gURLBar.popup.richlistbox.children[0]; + + info("Before override"); + is_element_hidden(result._url, "URL element should be hidden"); + is_element_visible(result._extra, "Extra element should be visible"); + + info("During override"); + EventUtils.synthesizeKey("VK_SHIFT" , { type: "keydown" }); + is_element_hidden(result._url, "URL element should be hidden"); + is_element_visible(result._extra, "Extra element should be visible"); + + EventUtils.synthesizeKey("VK_SHIFT" , { type: "keyup" }); + + gURLBar.popup.hidePopup(); + yield promisePopupHidden(gURLBar.popup); +}); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/test/general/browser.ini thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/test/general/browser.ini --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/test/general/browser.ini 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/test/general/browser.ini 2015-01-07 22:15:18.000000000 +0000 @@ -112,6 +112,7 @@ skip-if = e10s # Bug 1093153 - no about:home support yet [browser_aboutSyncProgress.js] [browser_action_keyword.js] +[browser_action_keyword_override.js] [browser_action_searchengine.js] [browser_action_searchengine_alias.js] [browser_addKeywordSearch.js] diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/test/newtab/browser.ini thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/test/newtab/browser.ini --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/test/newtab/browser.ini 2015-01-06 13:14:18.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/test/newtab/browser.ini 2015-01-07 22:15:19.000000000 +0000 @@ -45,4 +45,3 @@ [browser_newtab_undo.js] [browser_newtab_unpin.js] [browser_newtab_update.js] -skip-if = true # Bug 1008029 diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/urlbarBindings.xml thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/urlbarBindings.xml --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/content/urlbarBindings.xml 2015-01-06 13:14:19.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/content/urlbarBindings.xml 2015-01-07 22:15:19.000000000 +0000 @@ -923,6 +923,12 @@ var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController); // Check for unmodified left-click, and use default behavior + var searchBar = BrowserSearch.searchBar; + searchBar.telemetrySearchDetails = { + index: controller.selection.currentIndex, + kind: "mouse" + }; + if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey && !aEvent.altKey && !aEvent.metaKey) { controller.handleEnter(true); @@ -930,7 +936,6 @@ } // Check for middle-click or modified clicks on the search bar - var searchBar = BrowserSearch.searchBar; if (searchBar && searchBar.textbox == this.mInput) { // Handle search bar popup clicks var search = controller.getValueAt(this.selectedIndex); @@ -996,7 +1001,7 @@ diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/jar.mn thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/jar.mn --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/base/jar.mn 2015-01-06 13:14:19.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/base/jar.mn 2015-01-07 22:15:19.000000000 +0000 @@ -77,7 +77,6 @@ * content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml) * content/browser/chatWindow.xul (content/chatWindow.xul) content/browser/content.js (content/content.js) - content/browser/content-UITour.js (content/content-UITour.js) content/browser/defaultthemes/1.footer.jpg (content/defaultthemes/1.footer.jpg) content/browser/defaultthemes/1.header.jpg (content/defaultthemes/1.header.jpg) content/browser/defaultthemes/1.icon.jpg (content/defaultthemes/1.icon.jpg) diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/conversation.html thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/conversation.html --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/conversation.html 2015-01-06 13:14:19.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/conversation.html 2015-01-07 22:15:19.000000000 +0000 @@ -20,7 +20,7 @@ - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/contacts.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/contacts.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/contacts.js 2015-01-06 13:14:19.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/contacts.js 2015-01-07 22:15:19.000000000 +0000 @@ -81,7 +81,7 @@ contact[field][0].value = value; }; - const ContactDropdown = React.createClass({displayName: 'ContactDropdown', + const ContactDropdown = React.createClass({displayName: "ContactDropdown", propTypes: { handleAction: React.PropTypes.func.isRequired, canEdit: React.PropTypes.bool @@ -124,36 +124,36 @@ : "block_contact_menu_button"; return ( - React.DOM.ul({className: cx({ "dropdown-menu": true, + React.createElement("ul", {className: cx({ "dropdown-menu": true, "dropdown-menu-up": this.state.openDirUp })}, - React.DOM.li({className: cx({ "dropdown-menu-item": true, + React.createElement("li", {className: cx({ "dropdown-menu-item": true, "disabled": this.props.blocked }), onClick: this.onItemClick, - 'data-action': "video-call"}, - React.DOM.i({className: "icon icon-video-call"}), + "data-action": "video-call"}, + React.createElement("i", {className: "icon icon-video-call"}), mozL10n.get("video_call_menu_button") ), - React.DOM.li({className: cx({ "dropdown-menu-item": true, + React.createElement("li", {className: cx({ "dropdown-menu-item": true, "disabled": this.props.blocked }), - onClick: this.onItemClick, 'data-action': "audio-call"}, - React.DOM.i({className: "icon icon-audio-call"}), + onClick: this.onItemClick, "data-action": "audio-call"}, + React.createElement("i", {className: "icon icon-audio-call"}), mozL10n.get("audio_call_menu_button") ), - React.DOM.li({className: cx({ "dropdown-menu-item": true, + React.createElement("li", {className: cx({ "dropdown-menu-item": true, "disabled": !this.props.canEdit }), - onClick: this.onItemClick, 'data-action': "edit"}, - React.DOM.i({className: "icon icon-edit"}), + onClick: this.onItemClick, "data-action": "edit"}, + React.createElement("i", {className: "icon icon-edit"}), mozL10n.get("edit_contact_menu_button") ), - React.DOM.li({className: "dropdown-menu-item", - onClick: this.onItemClick, 'data-action': blockAction}, - React.DOM.i({className: "icon icon-" + blockAction}), + React.createElement("li", {className: "dropdown-menu-item", + onClick: this.onItemClick, "data-action": blockAction}, + React.createElement("i", {className: "icon icon-" + blockAction}), mozL10n.get(blockLabel) ), - React.DOM.li({className: cx({ "dropdown-menu-item": true, + React.createElement("li", {className: cx({ "dropdown-menu-item": true, "disabled": !this.props.canEdit }), - onClick: this.onItemClick, 'data-action': "remove"}, - React.DOM.i({className: "icon icon-remove"}), + onClick: this.onItemClick, "data-action": "remove"}, + React.createElement("i", {className: "icon icon-remove"}), mozL10n.get("remove_contact_menu_button") ) ) @@ -161,7 +161,7 @@ } }); - const ContactDetail = React.createClass({displayName: 'ContactDetail', + const ContactDetail = React.createClass({displayName: "ContactDetail", getInitialState: function() { return { showMenu: false, @@ -231,23 +231,23 @@ }); return ( - React.DOM.li({className: contactCSSClass, onMouseLeave: this.hideDropdownMenu}, - React.DOM.div({className: "avatar"}), - React.DOM.div({className: "details"}, - React.DOM.div({className: "username"}, React.DOM.strong(null, names.firstName), " ", names.lastName, - React.DOM.i({className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}), - React.DOM.i({className: cx({"icon icon-blocked": this.props.contact.blocked})}) + React.createElement("li", {className: contactCSSClass, onMouseLeave: this.hideDropdownMenu}, + React.createElement("div", {className: "avatar"}), + React.createElement("div", {className: "details"}, + React.createElement("div", {className: "username"}, React.createElement("strong", null, names.firstName), " ", names.lastName, + React.createElement("i", {className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}), + React.createElement("i", {className: cx({"icon icon-blocked": this.props.contact.blocked})}) ), - React.DOM.div({className: "email"}, email.value) + React.createElement("div", {className: "email"}, email.value) ), - React.DOM.div({className: "icons"}, - React.DOM.i({className: "icon icon-video", + React.createElement("div", {className: "icons"}, + React.createElement("i", {className: "icon icon-video", onClick: this.handleAction.bind(null, "video-call")}), - React.DOM.i({className: "icon icon-caret-down", + React.createElement("i", {className: "icon icon-caret-down", onClick: this.showDropdownMenu}) ), this.state.showMenu - ? ContactDropdown({handleAction: this.handleAction, + ? React.createElement(ContactDropdown, {handleAction: this.handleAction, canEdit: this.canEdit(), blocked: this.props.contact.blocked}) : null @@ -257,7 +257,7 @@ } }); - const ContactsList = React.createClass({displayName: 'ContactsList', + const ContactsList = React.createClass({displayName: "ContactsList", mixins: [ React.addons.LinkedStateMixin, loop.shared.mixins.WindowCloseMixin @@ -467,7 +467,7 @@ let cx = React.addons.classSet; let viewForItem = item => { - return ContactDetail({key: item._guid, contact: item, + return React.createElement(ContactDetail, {key: item._guid, contact: item, handleContactAction: this.handleContactAction}) }; @@ -494,33 +494,33 @@ } return ( - React.DOM.div(null, - React.DOM.div({className: "content-area"}, - ButtonGroup(null, - Button({caption: this.state.importBusy + React.createElement("div", null, + React.createElement("div", {className: "content-area"}, + React.createElement(ButtonGroup, null, + React.createElement(Button, {caption: this.state.importBusy ? mozL10n.get("importing_contacts_progress_button") : mozL10n.get("import_contacts_button"), disabled: this.state.importBusy, onClick: this.handleImportButtonClick}, - React.DOM.div({className: cx({"contact-import-spinner": true, + React.createElement("div", {className: cx({"contact-import-spinner": true, spinner: true, busy: this.state.importBusy})}) ), - Button({caption: mozL10n.get("new_contact_button"), + React.createElement(Button, {caption: mozL10n.get("new_contact_button"), onClick: this.handleAddContactButtonClick}) ), showFilter ? - React.DOM.input({className: "contact-filter", + React.createElement("input", {className: "contact-filter", placeholder: mozL10n.get("contacts_search_placesholder"), valueLink: this.linkState("filter")}) : null ), - React.DOM.ul({className: "contact-list"}, + React.createElement("ul", {className: "contact-list"}, shownContacts.available ? shownContacts.available.sort(this.sortContacts).map(viewForItem) : null, shownContacts.blocked && shownContacts.blocked.length > 0 ? - React.DOM.div({className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) : + React.createElement("div", {className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) : null, shownContacts.blocked ? shownContacts.blocked.sort(this.sortContacts).map(viewForItem) : @@ -531,7 +531,7 @@ } }); - const ContactDetailsForm = React.createClass({displayName: 'ContactDetailsForm', + const ContactDetailsForm = React.createClass({displayName: "ContactDetailsForm", mixins: [React.addons.LinkedStateMixin], propTypes: { @@ -628,27 +628,27 @@ let phoneOrEmailRequired = !this.state.email && !this.state.tel; return ( - React.DOM.div({className: "content-area contact-form"}, - React.DOM.header(null, this.props.mode == "add" + React.createElement("div", {className: "content-area contact-form"}, + React.createElement("header", null, this.props.mode == "add" ? mozL10n.get("add_contact_button") : mozL10n.get("edit_contact_title")), - React.DOM.label(null, mozL10n.get("edit_contact_name_label")), - React.DOM.input({ref: "name", required: true, pattern: "\\s*\\S.*", type: "text", + React.createElement("label", null, mozL10n.get("edit_contact_name_label")), + React.createElement("input", {ref: "name", required: true, pattern: "\\s*\\S.*", type: "text", className: cx({pristine: this.state.pristine}), valueLink: this.linkState("name")}), - React.DOM.label(null, mozL10n.get("edit_contact_email_label")), - React.DOM.input({ref: "email", type: "email", required: phoneOrEmailRequired, + React.createElement("label", null, mozL10n.get("edit_contact_email_label")), + React.createElement("input", {ref: "email", type: "email", required: phoneOrEmailRequired, className: cx({pristine: this.state.pristine}), valueLink: this.linkState("email")}), - React.DOM.label(null, mozL10n.get("new_contact_fxos_phone_placeholder")), - React.DOM.input({ref: "tel", type: "tel", required: phoneOrEmailRequired, + React.createElement("label", null, mozL10n.get("new_contact_fxos_phone_placeholder")), + React.createElement("input", {ref: "tel", type: "tel", required: phoneOrEmailRequired, className: cx({pristine: this.state.pristine}), valueLink: this.linkState("tel")}), - ButtonGroup(null, - Button({additionalClass: "button-cancel", + React.createElement(ButtonGroup, null, + React.createElement(Button, {additionalClass: "button-cancel", caption: mozL10n.get("cancel_button"), onClick: this.handleCancelButtonClick}), - Button({additionalClass: "button-accept", + React.createElement(Button, {additionalClass: "button-accept", caption: this.props.mode == "add" ? mozL10n.get("add_contact_button") : mozL10n.get("edit_contact_done_button"), diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/conversation.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/conversation.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/conversation.js 2015-01-06 13:14:19.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/conversation.js 2015-01-07 22:15:19.000000000 +0000 @@ -26,7 +26,7 @@ * Master controller view for handling if incoming or outgoing calls are * in progress, and hence, which view to display. */ - var AppControllerView = React.createClass({displayName: 'AppControllerView', + var AppControllerView = React.createClass({displayName: "AppControllerView", mixins: [Backbone.Events, sharedMixins.WindowCloseMixin], propTypes: { @@ -64,7 +64,7 @@ render: function() { switch(this.state.windowType) { case "incoming": { - return (IncomingConversationView({ + return (React.createElement(IncomingConversationView, { client: this.props.client, conversation: this.props.conversation, sdk: this.props.sdk, @@ -73,21 +73,21 @@ )); } case "outgoing": { - return (OutgoingConversationView({ + return (React.createElement(OutgoingConversationView, { store: this.props.conversationStore, dispatcher: this.props.dispatcher, feedbackStore: this.props.feedbackStore} )); } case "room": { - return (DesktopRoomConversationView({ + return (React.createElement(DesktopRoomConversationView, { dispatcher: this.props.dispatcher, roomStore: this.props.roomStore, feedbackStore: this.props.feedbackStore} )); } case "failed": { - return GenericFailureView({cancelCall: this.closeWindow}); + return React.createElement(GenericFailureView, {cancelCall: this.closeWindow}); } default: { // If we don't have a windowType, we don't know what we are yet, @@ -183,7 +183,7 @@ dispatcher.dispatch(new sharedActions.WindowUnload()); }); - React.renderComponent(AppControllerView({ + React.render(React.createElement(AppControllerView, { conversationAppStore: conversationAppStore, roomStore: roomStore, feedbackStore: feedbackStore, diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/conversation.jsx thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/conversation.jsx --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/conversation.jsx 2015-01-06 13:14:19.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/conversation.jsx 2015-01-07 22:15:19.000000000 +0000 @@ -183,7 +183,7 @@ dispatcher.dispatch(new sharedActions.WindowUnload()); }); - React.renderComponent(@" or "+" var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/; - var IncomingCallView = React.createClass({displayName: 'IncomingCallView', + var IncomingCallView = React.createClass({displayName: "IncomingCallView", mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin], propTypes: { @@ -213,29 +213,29 @@ }); return ( - React.DOM.div({className: "call-window"}, - CallIdentifierView({video: this.props.video, + React.createElement("div", {className: "call-window"}, + React.createElement(CallIdentifierView, {video: this.props.video, peerIdentifier: this.props.model.getCallIdentifier(), urlCreationDate: this.props.model.get("urlCreationDate"), showIcons: true}), - React.DOM.div({className: "btn-group call-action-group"}, + React.createElement("div", {className: "btn-group call-action-group"}, - React.DOM.div({className: "fx-embedded-call-button-spacer"}), + React.createElement("div", {className: "fx-embedded-call-button-spacer"}), - React.DOM.div({className: "btn-chevron-menu-group"}, - React.DOM.div({className: "btn-group-chevron"}, - React.DOM.div({className: "btn-group"}, + React.createElement("div", {className: "btn-chevron-menu-group"}, + React.createElement("div", {className: "btn-group-chevron"}, + React.createElement("div", {className: "btn-group"}, - React.DOM.button({className: "btn btn-decline", + React.createElement("button", {className: "btn btn-decline", onClick: this._handleDecline}, mozL10n.get("incoming_call_cancel_button") ), - React.DOM.div({className: "btn-chevron", onClick: this.toggleDropdownMenu}) + React.createElement("div", {className: "btn-chevron", onClick: this.toggleDropdownMenu}) ), - React.DOM.ul({className: dropdownMenuClassesDecline}, - React.DOM.li({className: "btn-block", onClick: this._handleDeclineBlock}, + React.createElement("ul", {className: dropdownMenuClassesDecline}, + React.createElement("li", {className: "btn-block", onClick: this._handleDeclineBlock}, mozL10n.get("incoming_call_cancel_and_block_button") ) ) @@ -243,11 +243,11 @@ ) ), - React.DOM.div({className: "fx-embedded-call-button-spacer"}), + React.createElement("div", {className: "fx-embedded-call-button-spacer"}), - AcceptCallButton({mode: this._answerModeProps()}), + React.createElement(AcceptCallButton, {mode: this._answerModeProps()}), - React.DOM.div({className: "fx-embedded-call-button-spacer"}) + React.createElement("div", {className: "fx-embedded-call-button-spacer"}) ) ) @@ -260,7 +260,7 @@ * Incoming call view accept button, renders different primary actions * (answer with video / with audio only) based on the props received **/ - var AcceptCallButton = React.createClass({displayName: 'AcceptCallButton', + var AcceptCallButton = React.createClass({displayName: "AcceptCallButton", propTypes: { mode: React.PropTypes.object.isRequired, @@ -270,17 +270,17 @@ var mode = this.props.mode; return ( /* jshint ignore:start */ - React.DOM.div({className: "btn-chevron-menu-group"}, - React.DOM.div({className: "btn-group"}, - React.DOM.button({className: "btn btn-accept", + React.createElement("div", {className: "btn-chevron-menu-group"}, + React.createElement("div", {className: "btn-group"}, + React.createElement("button", {className: "btn btn-accept", onClick: mode.primary.handler, title: mozL10n.get(mode.primary.tooltip)}, - React.DOM.span({className: "fx-embedded-answer-btn-text"}, + React.createElement("span", {className: "fx-embedded-answer-btn-text"}, mozL10n.get("incoming_call_accept_button") ), - React.DOM.span({className: mode.primary.className}) + React.createElement("span", {className: mode.primary.className}) ), - React.DOM.div({className: mode.secondary.className, + React.createElement("div", {className: mode.secondary.className, onClick: mode.secondary.handler, title: mozL10n.get(mode.secondary.tooltip)} ) @@ -297,7 +297,7 @@ * XXX Based on CallFailedView, but built specially until we flux-ify the * incoming call views (bug 1088672). */ - var GenericFailureView = React.createClass({displayName: 'GenericFailureView', + var GenericFailureView = React.createClass({displayName: "GenericFailureView", mixins: [sharedMixins.AudioMixin], propTypes: { @@ -312,11 +312,11 @@ document.title = mozL10n.get("generic_failure_title"); return ( - React.DOM.div({className: "call-window"}, - React.DOM.h2(null, mozL10n.get("generic_failure_title")), + React.createElement("div", {className: "call-window"}, + React.createElement("h2", null, mozL10n.get("generic_failure_title")), - React.DOM.div({className: "btn-group call-action-group"}, - React.DOM.button({className: "btn btn-cancel", + React.createElement("div", {className: "btn-group call-action-group"}, + React.createElement("button", {className: "btn btn-cancel", onClick: this.props.cancelCall}, mozL10n.get("cancel_button") ) @@ -332,7 +332,7 @@ * * At the moment, it does more than that, these parts need refactoring out. */ - var IncomingConversationView = React.createClass({displayName: 'IncomingConversationView', + var IncomingConversationView = React.createClass({displayName: "IncomingConversationView", mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin], propTypes: { @@ -385,7 +385,7 @@ document.title = mozL10n.get("incoming_call_title2"); return ( - IncomingCallView({ + React.createElement(IncomingCallView, { model: this.props.conversation, video: this.props.conversation.hasVideoStream("incoming")} ) @@ -397,7 +397,7 @@ var callType = this.props.conversation.get("selectedCallType"); return ( - sharedViews.ConversationView({ + React.createElement(sharedViews.ConversationView, { initiate: true, sdk: this.props.sdk, model: this.props.conversation, @@ -408,7 +408,7 @@ case "end": { // XXX To be handled with the "failed" view state when bug 1047410 lands if (this.state.callFailed) { - return GenericFailureView({ + return React.createElement(GenericFailureView, { cancelCall: this.closeWindow.bind(this)} ); } @@ -418,7 +418,7 @@ this.play("terminated"); return ( - sharedViews.FeedbackView({ + React.createElement(sharedViews.FeedbackView, { feedbackStore: this.props.feedbackStore, onAfterFeedbackReceived: this.closeWindow.bind(this)} ) @@ -426,7 +426,7 @@ } case "close": { this.closeWindow(); - return (React.DOM.div(null)); + return (React.createElement("div", null)); } } }, @@ -646,7 +646,7 @@ * View for pending conversations. Displays a cancel button and appropriate * pending/ringing strings. */ - var PendingConversationView = React.createClass({displayName: 'PendingConversationView', + var PendingConversationView = React.createClass({displayName: "PendingConversationView", mixins: [sharedMixins.AudioMixin], propTypes: { @@ -686,12 +686,12 @@ }); return ( - ConversationDetailView({contact: this.props.contact}, + React.createElement(ConversationDetailView, {contact: this.props.contact}, - React.DOM.p({className: "btn-label"}, pendingStateString), + React.createElement("p", {className: "btn-label"}, pendingStateString), - React.DOM.div({className: "btn-group call-action-group"}, - React.DOM.button({className: btnCancelStyles, + React.createElement("div", {className: "btn-group call-action-group"}, + React.createElement("button", {className: btnCancelStyles, onClick: this.cancelCall}, mozL10n.get("initiate_call_cancel_button") ) @@ -705,7 +705,7 @@ /** * Call failed view. Displayed when a call fails. */ - var CallFailedView = React.createClass({displayName: 'CallFailedView', + var CallFailedView = React.createClass({displayName: "CallFailedView", mixins: [ Backbone.Events, sharedMixins.AudioMixin, @@ -758,7 +758,7 @@ if (!this.state.emailLinkError) { return; } - return React.DOM.p({className: "error"}, mozL10n.get("unable_retrieve_url")); + return React.createElement("p", {className: "error"}, mozL10n.get("unable_retrieve_url")); }, retryCall: function() { @@ -783,23 +783,23 @@ render: function() { return ( - React.DOM.div({className: "call-window"}, - React.DOM.h2(null, mozL10n.get("generic_failure_title")), + React.createElement("div", {className: "call-window"}, + React.createElement("h2", null, mozL10n.get("generic_failure_title")), - React.DOM.p({className: "btn-label"}, mozL10n.get("generic_failure_with_reason2")), + React.createElement("p", {className: "btn-label"}, mozL10n.get("generic_failure_with_reason2")), this._renderError(), - React.DOM.div({className: "btn-group call-action-group"}, - React.DOM.button({className: "btn btn-cancel", + React.createElement("div", {className: "btn-group call-action-group"}, + React.createElement("button", {className: "btn btn-cancel", onClick: this.cancelCall}, mozL10n.get("cancel_button") ), - React.DOM.button({className: "btn btn-info btn-retry", + React.createElement("button", {className: "btn btn-info btn-retry", onClick: this.retryCall}, mozL10n.get("retry_call_button") ), - React.DOM.button({className: "btn btn-info btn-email", + React.createElement("button", {className: "btn btn-info btn-email", onClick: this.emailLink, disabled: this.state.emailLinkButtonDisabled}, mozL10n.get("share_button2") @@ -810,7 +810,7 @@ } }); - var OngoingConversationView = React.createClass({displayName: 'OngoingConversationView', + var OngoingConversationView = React.createClass({displayName: "OngoingConversationView", propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, video: React.PropTypes.object, @@ -924,15 +924,15 @@ }); return ( - React.DOM.div({className: "video-layout-wrapper"}, - React.DOM.div({className: "conversation"}, - React.DOM.div({className: "media nested"}, - React.DOM.div({className: "video_wrapper remote_wrapper"}, - React.DOM.div({className: "video_inner remote"}) + React.createElement("div", {className: "video-layout-wrapper"}, + React.createElement("div", {className: "conversation"}, + React.createElement("div", {className: "media nested"}, + React.createElement("div", {className: "video_wrapper remote_wrapper"}, + React.createElement("div", {className: "video_inner remote"}) ), - React.DOM.div({className: localStreamClasses}) + React.createElement("div", {className: localStreamClasses}) ), - loop.shared.views.ConversationToolbar({ + React.createElement(loop.shared.views.ConversationToolbar, { video: this.props.video, audio: this.props.audio, publishStream: this.publishStream, @@ -947,7 +947,7 @@ * Master View Controller for outgoing calls. This manages * the different views that need displaying. */ - var OutgoingConversationView = React.createClass({displayName: 'OutgoingConversationView', + var OutgoingConversationView = React.createClass({displayName: "OutgoingConversationView", mixins: [ sharedMixins.AudioMixin, Backbone.Events @@ -995,7 +995,7 @@ document.title = mozL10n.get("conversation_has_ended"); return ( - sharedViews.FeedbackView({ + React.createElement(sharedViews.FeedbackView, { feedbackStore: this.props.feedbackStore, onAfterFeedbackReceived: this._closeWindow.bind(this)} ) @@ -1009,14 +1009,14 @@ return null; } case CALL_STATES.TERMINATED: { - return (CallFailedView({ + return (React.createElement(CallFailedView, { dispatcher: this.props.dispatcher, store: this.props.store, contact: this.state.contact} )); } case CALL_STATES.ONGOING: { - return (OngoingConversationView({ + return (React.createElement(OngoingConversationView, { dispatcher: this.props.dispatcher, video: {enabled: !this.state.videoMuted}, audio: {enabled: !this.state.audioMuted}} @@ -1032,7 +1032,7 @@ return null; } default: { - return (PendingConversationView({ + return (React.createElement(PendingConversationView, { dispatcher: this.props.dispatcher, callState: this.state.callState, contact: this.state.contact, diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/panel.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/panel.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/panel.js 2015-01-06 13:14:19.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/panel.js 2015-01-07 22:15:19.000000000 +0000 @@ -21,7 +21,7 @@ var ContactsList = loop.contacts.ContactsList; var ContactDetailsForm = loop.contacts.ContactDetailsForm; - var TabView = React.createClass({displayName: 'TabView', + var TabView = React.createClass({displayName: "TabView", propTypes: { buttonsHidden: React.PropTypes.array, // The selectedTab prop is used by the UI showcase. @@ -66,28 +66,28 @@ var isSelected = (this.state.selectedTab == tabName); if (!tab.props.hidden) { tabButtons.push( - React.DOM.li({className: cx({selected: isSelected}), + React.createElement("li", {className: cx({selected: isSelected}), key: i, - 'data-tab-name': tabName, + "data-tab-name": tabName, onClick: this.handleSelectTab}) ); } tabs.push( - React.DOM.div({key: i, className: cx({tab: true, selected: isSelected})}, + React.createElement("div", {key: i, className: cx({tab: true, selected: isSelected})}, tab.props.children ) ); }, this); return ( - React.DOM.div({className: "tab-view-container"}, - React.DOM.ul({className: "tab-view"}, tabButtons), + React.createElement("div", {className: "tab-view-container"}, + React.createElement("ul", {className: "tab-view"}, tabButtons), tabs ) ); } }); - var Tab = React.createClass({displayName: 'Tab', + var Tab = React.createClass({displayName: "Tab", render: function() { return null; } @@ -96,7 +96,7 @@ /** * Availability drop down menu subview. */ - var AvailabilityDropdown = React.createClass({displayName: 'AvailabilityDropdown', + var AvailabilityDropdown = React.createClass({displayName: "AvailabilityDropdown", mixins: [sharedMixins.DropdownMenuMixin], getInitialState: function() { @@ -142,22 +142,22 @@ mozL10n.get("display_name_available_status"); return ( - React.DOM.div({className: "dropdown"}, - React.DOM.p({className: "dnd-status", onClick: this.showDropdownMenu}, - React.DOM.span(null, availabilityText), - React.DOM.i({className: availabilityStatus}) + React.createElement("div", {className: "dropdown"}, + React.createElement("p", {className: "dnd-status", onClick: this.showDropdownMenu}, + React.createElement("span", null, availabilityText), + React.createElement("i", {className: availabilityStatus}) ), - React.DOM.ul({className: availabilityDropdown, + React.createElement("ul", {className: availabilityDropdown, onMouseLeave: this.hideDropdownMenu}, - React.DOM.li({onClick: this.changeAvailability("available"), + React.createElement("li", {onClick: this.changeAvailability("available"), className: "dropdown-menu-item dnd-make-available"}, - React.DOM.i({className: "status status-available"}), - React.DOM.span(null, mozL10n.get("display_name_available_status")) + React.createElement("i", {className: "status status-available"}), + React.createElement("span", null, mozL10n.get("display_name_available_status")) ), - React.DOM.li({onClick: this.changeAvailability("do-not-disturb"), + React.createElement("li", {onClick: this.changeAvailability("do-not-disturb"), className: "dropdown-menu-item dnd-make-unavailable"}, - React.DOM.i({className: "status status-dnd"}), - React.DOM.span(null, mozL10n.get("display_name_dnd_status")) + React.createElement("i", {className: "status status-dnd"}), + React.createElement("span", null, mozL10n.get("display_name_dnd_status")) ) ) ) @@ -165,7 +165,7 @@ } }); - var GettingStartedView = React.createClass({displayName: 'GettingStartedView', + var GettingStartedView = React.createClass({displayName: "GettingStartedView", mixins: [sharedMixins.WindowCloseMixin], handleButtonClick: function() { @@ -181,13 +181,13 @@ return null; } return ( - React.DOM.div({id: "fte-getstarted"}, - React.DOM.header({id: "fte-title"}, + React.createElement("div", {id: "fte-getstarted"}, + React.createElement("header", {id: "fte-title"}, mozL10n.get("first_time_experience_title", { "clientShortname": mozL10n.get("clientShortname2") }) ), - Button({htmlId: "fte-button", + React.createElement(Button, {htmlId: "fte-button", onClick: this.handleButtonClick, caption: mozL10n.get("first_time_experience_button_label")}) ) @@ -195,7 +195,7 @@ } }); - var ToSView = React.createClass({displayName: 'ToSView', + var ToSView = React.createClass({displayName: "ToSView", getInitialState: function() { var getPref = navigator.mozLoop.getLoopPref.bind(navigator.mozLoop); @@ -212,28 +212,28 @@ var privacy_notice_url = navigator.mozLoop.getLoopPref('legal.privacy_url'); var tosHTML = mozL10n.get("legal_text_and_links3", { "clientShortname": mozL10n.get("clientShortname2"), - "terms_of_use": React.renderComponentToStaticMarkup( - React.DOM.a({href: terms_of_use_url, target: "_blank"}, + "terms_of_use": React.renderToStaticMarkup( + React.createElement("a", {href: terms_of_use_url, target: "_blank"}, mozL10n.get("legal_text_tos") ) ), - "privacy_notice": React.renderComponentToStaticMarkup( - React.DOM.a({href: privacy_notice_url, target: "_blank"}, + "privacy_notice": React.renderToStaticMarkup( + React.createElement("a", {href: privacy_notice_url, target: "_blank"}, mozL10n.get("legal_text_privacy") ) ), }); - return React.DOM.div({id: "powered-by-wrapper"}, - React.DOM.p({id: "powered-by"}, + return React.createElement("div", {id: "powered-by-wrapper"}, + React.createElement("p", {id: "powered-by"}, mozL10n.get("powered_by_beforeLogo"), - React.DOM.img({id: "powered-by-logo", className: locale}), + React.createElement("img", {id: "powered-by-logo", className: locale}), mozL10n.get("powered_by_afterLogo") ), - React.DOM.p({className: "terms-service", + React.createElement("p", {className: "terms-service", dangerouslySetInnerHTML: {__html: tosHTML}}) ); } else { - return React.DOM.div(null); + return React.createElement("div", null); } } }); @@ -241,7 +241,7 @@ /** * Panel settings (gear) menu entry. */ - var SettingsDropdownEntry = React.createClass({displayName: 'SettingsDropdownEntry', + var SettingsDropdownEntry = React.createClass({displayName: "SettingsDropdownEntry", propTypes: { onClick: React.PropTypes.func.isRequired, label: React.PropTypes.string.isRequired, @@ -258,11 +258,11 @@ return null; } return ( - React.DOM.li({onClick: this.props.onClick, className: "dropdown-menu-item"}, + React.createElement("li", {onClick: this.props.onClick, className: "dropdown-menu-item"}, this.props.icon ? - React.DOM.i({className: "icon icon-" + this.props.icon}) : + React.createElement("i", {className: "icon icon-" + this.props.icon}) : null, - React.DOM.span(null, this.props.label) + React.createElement("span", null, this.props.label) ) ); } @@ -271,7 +271,7 @@ /** * Panel settings (gear) menu. */ - var SettingsDropdown = React.createClass({displayName: 'SettingsDropdown', + var SettingsDropdown = React.createClass({displayName: "SettingsDropdown", mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.WindowCloseMixin], handleClickSettingsEntry: function() { @@ -315,29 +315,29 @@ } return ( - React.DOM.div({className: "settings-menu dropdown"}, - React.DOM.a({className: "button-settings", onClick: this.showDropdownMenu, + React.createElement("div", {className: "settings-menu dropdown"}, + React.createElement("a", {className: "button-settings", onClick: this.showDropdownMenu, title: mozL10n.get("settings_menu_button_tooltip")}), - React.DOM.ul({className: cx({"dropdown-menu": true, hide: !this.state.showMenu}), + React.createElement("ul", {className: cx({"dropdown-menu": true, hide: !this.state.showMenu}), onMouseLeave: this.hideDropdownMenu}, - SettingsDropdownEntry({label: mozL10n.get("settings_menu_item_settings"), + React.createElement(SettingsDropdownEntry, {label: mozL10n.get("settings_menu_item_settings"), onClick: this.handleClickSettingsEntry, displayed: false, icon: "settings"}), - SettingsDropdownEntry({label: mozL10n.get("settings_menu_item_account"), + React.createElement(SettingsDropdownEntry, {label: mozL10n.get("settings_menu_item_account"), onClick: this.handleClickAccountEntry, icon: "account", displayed: this._isSignedIn()}), - SettingsDropdownEntry({icon: "tour", + React.createElement(SettingsDropdownEntry, {icon: "tour", label: mozL10n.get("tour_label"), onClick: this.openGettingStartedTour}), - SettingsDropdownEntry({label: this._isSignedIn() ? + React.createElement(SettingsDropdownEntry, {label: this._isSignedIn() ? mozL10n.get("settings_menu_item_signout") : mozL10n.get("settings_menu_item_signin"), onClick: this.handleClickAuthEntry, displayed: navigator.mozLoop.fxAEnabled, icon: this._isSignedIn() ? "signout" : "signin"}), - SettingsDropdownEntry({label: mozL10n.get("help_label"), + React.createElement(SettingsDropdownEntry, {label: mozL10n.get("help_label"), onClick: this.handleHelpEntry, icon: "help"}) ) @@ -349,7 +349,7 @@ /** * Call url result view. */ - var CallUrlResult = React.createClass({displayName: 'CallUrlResult', + var CallUrlResult = React.createClass({displayName: "CallUrlResult", mixins: [sharedMixins.DocumentVisibilityMixin], propTypes: { @@ -467,26 +467,26 @@ // from the react lib. var cx = React.addons.classSet; return ( - React.DOM.div({className: "generate-url"}, - React.DOM.header({id: "share-link-header"}, mozL10n.get("share_link_header_text")), - React.DOM.div({className: "generate-url-stack"}, - React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true", + React.createElement("div", {className: "generate-url"}, + React.createElement("header", {id: "share-link-header"}, mozL10n.get("share_link_header_text")), + React.createElement("div", {className: "generate-url-stack"}, + React.createElement("input", {type: "url", value: this.state.callUrl, readOnly: "true", onCopy: this.handleLinkExfiltration, className: cx({"generate-url-input": true, pending: this.state.pending, // Used in functional testing, signals that // call url was received from loop server callUrl: !this.state.pending})}), - React.DOM.div({className: cx({"generate-url-spinner": true, + React.createElement("div", {className: cx({"generate-url-spinner": true, spinner: true, busy: this.state.pending})}) ), - ButtonGroup({additionalClass: "url-actions"}, - Button({additionalClass: "button-email", + React.createElement(ButtonGroup, {additionalClass: "url-actions"}, + React.createElement(Button, {additionalClass: "button-email", disabled: !this.state.callUrl, onClick: this.handleEmailButtonClick, caption: mozL10n.get("share_button")}), - Button({additionalClass: "button-copy", + React.createElement(Button, {additionalClass: "button-copy", disabled: !this.state.callUrl, onClick: this.handleCopyButtonClick, caption: this.state.copied ? mozL10n.get("copied_url_button") : @@ -500,7 +500,7 @@ /** * FxA sign in/up link component. */ - var AuthLink = React.createClass({displayName: 'AuthLink', + var AuthLink = React.createClass({displayName: "AuthLink", handleSignUpLinkClick: function() { navigator.mozLoop.logInToFxA(); }, @@ -510,8 +510,8 @@ return null; } return ( - React.DOM.p({className: "signin-link"}, - React.DOM.a({href: "#", onClick: this.handleSignUpLinkClick}, + React.createElement("p", {className: "signin-link"}, + React.createElement("a", {href: "#", onClick: this.handleSignUpLinkClick}, mozL10n.get("panel_footer_signin_or_signup_link") ) ) @@ -522,17 +522,17 @@ /** * FxA user identity (guest/authenticated) component. */ - var UserIdentity = React.createClass({displayName: 'UserIdentity', + var UserIdentity = React.createClass({displayName: "UserIdentity", render: function() { return ( - React.DOM.p({className: "user-identity"}, + React.createElement("p", {className: "user-identity"}, this.props.displayName ) ); } }); - var EditInPlace = React.createClass({displayName: 'EditInPlace', + var EditInPlace = React.createClass({displayName: "EditInPlace", mixins: [React.addons.LinkedStateMixin], propTypes: { @@ -588,15 +588,15 @@ render: function() { if (!this.state.edit) { return ( - React.DOM.span({className: "edit-in-place", onClick: this.handleTextClick, + React.createElement("span", {className: "edit-in-place", onClick: this.handleTextClick, title: mozL10n.get("rooms_name_this_room_tooltip2")}, this.state.text ) ); } return ( - React.DOM.form({onSubmit: this.handleFormSubmit}, - React.DOM.input({type: "text", valueLink: this.linkState("text"), + React.createElement("form", {onSubmit: this.handleFormSubmit}, + React.createElement("input", {type: "text", valueLink: this.linkState("text"), onClick: this.handleInputClick, onBlur: this.cancelEdit}) ) @@ -607,7 +607,7 @@ /** * Room list entry. */ - var RoomEntry = React.createClass({displayName: 'RoomEntry', + var RoomEntry = React.createClass({displayName: "RoomEntry", propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, room: React.PropTypes.instanceOf(loop.store.Room).isRequired @@ -690,19 +690,19 @@ }); return ( - React.DOM.div({className: roomClasses, onMouseLeave: this.handleMouseLeave, + React.createElement("div", {className: roomClasses, onMouseLeave: this.handleMouseLeave, onClick: this.handleClickEntry}, - React.DOM.h2(null, - React.DOM.span({className: "room-notification"}), - EditInPlace({text: room.roomName, onChange: this.renameRoom}), - React.DOM.button({className: copyButtonClasses, + React.createElement("h2", null, + React.createElement("span", {className: "room-notification"}), + React.createElement(EditInPlace, {text: room.roomName, onChange: this.renameRoom}), + React.createElement("button", {className: copyButtonClasses, title: mozL10n.get("rooms_list_copy_url_tooltip"), onClick: this.handleCopyButtonClick}), - React.DOM.button({className: "delete-link", + React.createElement("button", {className: "delete-link", title: mozL10n.get("rooms_list_delete_tooltip"), onClick: this.handleDeleteButtonClick}) ), - React.DOM.p(null, React.DOM.a({className: "room-url-link", href: "#"}, room.roomUrl)) + React.createElement("p", null, React.createElement("a", {className: "room-url-link", href: "#"}, room.roomUrl)) ) ); } @@ -711,7 +711,7 @@ /** * Room list. */ - var RoomList = React.createClass({displayName: 'RoomList', + var RoomList = React.createClass({displayName: "RoomList", mixins: [Backbone.Events, sharedMixins.WindowCloseMixin], propTypes: { @@ -776,19 +776,19 @@ } return ( - React.DOM.div({className: "rooms"}, - React.DOM.h1(null, this._getListHeading()), - React.DOM.div({className: "room-list"}, + React.createElement("div", {className: "rooms"}, + React.createElement("h1", null, this._getListHeading()), + React.createElement("div", {className: "room-list"}, this.state.rooms.map(function(room, i) { - return RoomEntry({ + return React.createElement(RoomEntry, { key: room.roomToken, dispatcher: this.props.dispatcher, room: room} ); }, this) ), - React.DOM.p(null, - React.DOM.button({className: "btn btn-info new-room-button", + React.createElement("p", null, + React.createElement("button", {className: "btn btn-info new-room-button", onClick: this.handleCreateButtonClick, disabled: this._hasPendingOperation()}, mozL10n.get("rooms_new_room_button_label") @@ -802,7 +802,7 @@ /** * Panel view. */ - var PanelView = React.createClass({displayName: 'PanelView', + var PanelView = React.createClass({displayName: "PanelView", propTypes: { notifications: React.PropTypes.object.isRequired, client: React.PropTypes.object.isRequired, @@ -894,23 +894,23 @@ _renderRoomsOrCallTab: function() { if (!this._roomsEnabled()) { return ( - Tab({name: "call"}, - React.DOM.div({className: "content-area"}, - CallUrlResult({client: this.props.client, + React.createElement(Tab, {name: "call"}, + React.createElement("div", {className: "content-area"}, + React.createElement(CallUrlResult, {client: this.props.client, notifications: this.props.notifications, callUrl: this.props.callUrl}), - ToSView(null) + React.createElement(ToSView, null) ) ) ); } return ( - Tab({name: "rooms"}, - RoomList({dispatcher: this.props.dispatcher, + React.createElement(Tab, {name: "rooms"}, + React.createElement(RoomList, {dispatcher: this.props.dispatcher, store: this.props.roomStore, userDisplayName: this._getUserDisplayName()}), - ToSView(null) + React.createElement(ToSView, null) ) ); }, @@ -950,11 +950,11 @@ if (!this.state.gettingStartedSeen) { return ( - React.DOM.div(null, - NotificationListView({notifications: this.props.notifications, + React.createElement("div", null, + React.createElement(NotificationListView, {notifications: this.props.notifications, clearOnDocumentHidden: true}), - GettingStartedView(null), - ToSView(null) + React.createElement(GettingStartedView, null), + React.createElement(ToSView, null) ) ); } @@ -966,38 +966,38 @@ } return ( - React.DOM.div(null, - NotificationListView({notifications: this.props.notifications, + React.createElement("div", null, + React.createElement(NotificationListView, {notifications: this.props.notifications, clearOnDocumentHidden: true}), - TabView({ref: "tabView", selectedTab: this.props.selectedTab, + React.createElement(TabView, {ref: "tabView", selectedTab: this.props.selectedTab, buttonsHidden: hideButtons}, this._renderRoomsOrCallTab(), - Tab({name: "contacts"}, - ContactsList({selectTab: this.selectTab, + React.createElement(Tab, {name: "contacts"}, + React.createElement(ContactsList, {selectTab: this.selectTab, startForm: this.startForm}) ), - Tab({name: "contacts_add", hidden: true}, - ContactDetailsForm({ref: "contacts_add", mode: "add", + React.createElement(Tab, {name: "contacts_add", hidden: true}, + React.createElement(ContactDetailsForm, {ref: "contacts_add", mode: "add", selectTab: this.selectTab}) ), - Tab({name: "contacts_edit", hidden: true}, - ContactDetailsForm({ref: "contacts_edit", mode: "edit", + React.createElement(Tab, {name: "contacts_edit", hidden: true}, + React.createElement(ContactDetailsForm, {ref: "contacts_edit", mode: "edit", selectTab: this.selectTab}) ), - Tab({name: "contacts_import", hidden: true}, - ContactDetailsForm({ref: "contacts_import", mode: "import", + React.createElement(Tab, {name: "contacts_import", hidden: true}, + React.createElement(ContactDetailsForm, {ref: "contacts_import", mode: "import", selectTab: this.selectTab}) ) ), - React.DOM.div({className: "footer"}, - React.DOM.div({className: "user-details"}, - UserIdentity({displayName: this._getUserDisplayName()}), - AvailabilityDropdown(null) + React.createElement("div", {className: "footer"}, + React.createElement("div", {className: "user-details"}, + React.createElement(UserIdentity, {displayName: this._getUserDisplayName()}), + React.createElement(AvailabilityDropdown, null) ), - React.DOM.div({className: "signin-details"}, - AuthLink(null), - React.DOM.div({className: "footer-signin-separator"}), - SettingsDropdown(null) + React.createElement("div", {className: "signin-details"}, + React.createElement(AuthLink, null), + React.createElement("div", {className: "footer-signin-separator"}), + React.createElement(SettingsDropdown, null) ) ) ) @@ -1021,7 +1021,7 @@ notifications: notifications }); - React.renderComponent(PanelView({ + React.render(React.createElement(PanelView, { client: client, notifications: notifications, roomStore: roomStore, diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/panel.jsx thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/panel.jsx --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/js/panel.jsx 2015-01-06 13:14:19.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/js/panel.jsx 2015-01-07 22:15:19.000000000 +0000 @@ -212,12 +212,12 @@ var privacy_notice_url = navigator.mozLoop.getLoopPref('legal.privacy_url'); var tosHTML = mozL10n.get("legal_text_and_links3", { "clientShortname": mozL10n.get("clientShortname2"), - "terms_of_use": React.renderComponentToStaticMarkup( + "terms_of_use": React.renderToStaticMarkup( {mozL10n.get("legal_text_tos")} ), - "privacy_notice": React.renderComponentToStaticMarkup( + "privacy_notice": React.renderToStaticMarkup( {mozL10n.get("legal_text_privacy")} @@ -1021,7 +1021,7 @@ notifications: notifications }); - React.renderComponent( - + diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/shared/js/feedbackViews.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/shared/js/feedbackViews.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/shared/js/feedbackViews.js 2015-01-06 13:14:20.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/shared/js/feedbackViews.js 2015-01-07 22:15:19.000000000 +0000 @@ -25,7 +25,7 @@ * Props: * - */ - var FeedbackLayout = React.createClass({displayName: 'FeedbackLayout', + var FeedbackLayout = React.createClass({displayName: "FeedbackLayout", propTypes: { children: React.PropTypes.component.isRequired, title: React.PropTypes.string.isRequired, @@ -33,19 +33,19 @@ }, render: function() { - var backButton = React.DOM.div(null); + var backButton = React.createElement("div", null); if (this.props.reset) { backButton = ( - React.DOM.button({className: "fx-embedded-btn-back", type: "button", + React.createElement("button", {className: "fx-embedded-btn-back", type: "button", onClick: this.props.reset}, "« ", l10n.get("feedback_back_button") ) ); } return ( - React.DOM.div({className: "feedback"}, + React.createElement("div", {className: "feedback"}, backButton, - React.DOM.h3(null, this.props.title), + React.createElement("h3", null, this.props.title), this.props.children ) ); @@ -55,7 +55,7 @@ /** * Detailed feedback form. */ - var FeedbackForm = React.createClass({displayName: 'FeedbackForm', + var FeedbackForm = React.createClass({displayName: "FeedbackForm", propTypes: { feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore), pending: React.PropTypes.bool, @@ -84,8 +84,8 @@ var categories = this._getCategories(); return Object.keys(categories).map(function(category, key) { return ( - React.DOM.label({key: key, className: "feedback-category-label"}, - React.DOM.input({type: "radio", ref: "category", name: "category", + React.createElement("label", {key: key, className: "feedback-category-label"}, + React.createElement("input", {type: "radio", ref: "category", name: "category", className: "feedback-category-radio", value: category, onChange: this.handleCategoryChange, @@ -149,12 +149,12 @@ var descriptionDisplayValue = this.state.category === "other" ? this.state.description : ""; return ( - FeedbackLayout({title: l10n.get("feedback_what_makes_you_sad"), + React.createElement(FeedbackLayout, {title: l10n.get("feedback_what_makes_you_sad"), reset: this.props.reset}, - React.DOM.form({onSubmit: this.handleFormSubmit}, + React.createElement("form", {onSubmit: this.handleFormSubmit}, this._getCategoryFields(), - React.DOM.p(null, - React.DOM.input({type: "text", ref: "description", name: "description", + React.createElement("p", null, + React.createElement("input", {type: "text", ref: "description", name: "description", className: "feedback-description", onChange: this.handleDescriptionFieldChange, onFocus: this.handleDescriptionFieldFocus, @@ -162,7 +162,7 @@ placeholder: l10n.get("feedback_custom_category_text_placeholder")}) ), - React.DOM.button({type: "submit", className: "btn btn-success", + React.createElement("button", {type: "submit", className: "btn btn-success", disabled: !this._isFormReady()}, l10n.get("feedback_submit_button") ) @@ -179,7 +179,7 @@ * - {Function} onAfterFeedbackReceived Function to execute after the * WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS timeout has elapsed */ - var FeedbackReceived = React.createClass({displayName: 'FeedbackReceived', + var FeedbackReceived = React.createClass({displayName: "FeedbackReceived", propTypes: { onAfterFeedbackReceived: React.PropTypes.func }, @@ -208,8 +208,8 @@ } } return ( - FeedbackLayout({title: l10n.get("feedback_thank_you_heading")}, - React.DOM.p({className: "info thank-you"}, + React.createElement(FeedbackLayout, {title: l10n.get("feedback_thank_you_heading")}, + React.createElement("p", {className: "info thank-you"}, l10n.get("feedback_window_will_close_in2", { countdown: this.state.countdown, num: this.state.countdown @@ -222,7 +222,7 @@ /** * Feedback view. */ - var FeedbackView = React.createClass({displayName: 'FeedbackView', + var FeedbackView = React.createClass({displayName: "FeedbackView", mixins: [Backbone.Events], propTypes: { @@ -283,12 +283,12 @@ default: case FEEDBACK_STATES.INIT: { return ( - FeedbackLayout({title: + React.createElement(FeedbackLayout, {title: l10n.get("feedback_call_experience_heading2")}, - React.DOM.div({className: "faces"}, - React.DOM.button({className: "face face-happy", + React.createElement("div", {className: "faces"}, + React.createElement("button", {className: "face face-happy", onClick: this.handleHappyClick}), - React.DOM.button({className: "face face-sad", + React.createElement("button", {className: "face face-sad", onClick: this.handleSadClick}) ) ) @@ -296,7 +296,7 @@ } case FEEDBACK_STATES.DETAILS: { return ( - FeedbackForm({ + React.createElement(FeedbackForm, { feedbackStore: this.props.feedbackStore, reset: this.reset, pending: this.state.feedbackState === FEEDBACK_STATES.PENDING}) @@ -311,7 +311,7 @@ this.state.error); } return ( - FeedbackReceived({ + React.createElement(FeedbackReceived, { onAfterFeedbackReceived: this.props.onAfterFeedbackReceived}) ); } diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/shared/js/otSdkDriver.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/shared/js/otSdkDriver.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/shared/js/otSdkDriver.js 2015-01-06 13:14:20.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/shared/js/otSdkDriver.js 2015-01-07 22:15:19.000000000 +0000 @@ -194,12 +194,22 @@ * https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html */ _onSessionDisconnected: function(event) { - // We only need to worry about the network disconnected reason here. - if (event.reason === "networkDisconnected") { - this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ - reason: FAILURE_REASONS.NETWORK_DISCONNECTED - })); + var reason; + switch (event.reason) { + case "networkDisconnected": + reason = FAILURE_REASONS.NETWORK_DISCONNECTED; + break; + case "forceDisconnected": + reason = FAILURE_REASONS.EXPIRED_OR_INVALID; + break; + default: + // Other cases don't need to be handled. + return; } + + this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ + reason: reason + })); }, /** diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/shared/js/views.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/shared/js/views.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/shared/js/views.js 2015-01-06 13:14:20.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/shared/js/views.js 2015-01-07 22:15:19.000000000 +0000 @@ -23,7 +23,7 @@ * - {Function} action Function to be executed on click. * - {Enabled} enabled Stream activation status (default: true). */ - var MediaControlButton = React.createClass({displayName: 'MediaControlButton', + var MediaControlButton = React.createClass({displayName: "MediaControlButton", propTypes: { scope: React.PropTypes.string.isRequired, type: React.PropTypes.string.isRequired, @@ -64,7 +64,7 @@ render: function() { return ( /* jshint ignore:start */ - React.DOM.button({className: this._getClasses(), + React.createElement("button", {className: this._getClasses(), title: this._getTitle(), onClick: this.handleClick}) /* jshint ignore:end */ @@ -75,7 +75,7 @@ /** * Conversation controls. */ - var ConversationToolbar = React.createClass({displayName: 'ConversationToolbar', + var ConversationToolbar = React.createClass({displayName: "ConversationToolbar", getDefaultProps: function() { return { video: {enabled: true, visible: true}, @@ -112,22 +112,22 @@ render: function() { var cx = React.addons.classSet; return ( - React.DOM.ul({className: "conversation-toolbar"}, - React.DOM.li({className: "conversation-toolbar-btn-box btn-hangup-entry"}, - React.DOM.button({className: "btn btn-hangup", onClick: this.handleClickHangup, + React.createElement("ul", {className: "conversation-toolbar"}, + React.createElement("li", {className: "conversation-toolbar-btn-box btn-hangup-entry"}, + React.createElement("button", {className: "btn btn-hangup", onClick: this.handleClickHangup, title: l10n.get("hangup_button_title"), disabled: !this.props.enableHangup}, this._getHangupButtonLabel() ) ), - React.DOM.li({className: "conversation-toolbar-btn-box"}, - MediaControlButton({action: this.handleToggleVideo, + React.createElement("li", {className: "conversation-toolbar-btn-box"}, + React.createElement(MediaControlButton, {action: this.handleToggleVideo, enabled: this.props.video.enabled, visible: this.props.video.visible, scope: "local", type: "video"}) ), - React.DOM.li({className: "conversation-toolbar-btn-box"}, - MediaControlButton({action: this.handleToggleAudio, + React.createElement("li", {className: "conversation-toolbar-btn-box"}, + React.createElement(MediaControlButton, {action: this.handleToggleAudio, enabled: this.props.audio.enabled, visible: this.props.audio.visible, scope: "local", type: "audio"}) @@ -140,7 +140,7 @@ /** * Conversation view. */ - var ConversationView = React.createClass({displayName: 'ConversationView', + var ConversationView = React.createClass({displayName: "ConversationView", mixins: [Backbone.Events, sharedMixins.AudioMixin], propTypes: { @@ -324,15 +324,15 @@ }); /* jshint ignore:start */ return ( - React.DOM.div({className: "video-layout-wrapper"}, - React.DOM.div({className: "conversation"}, - React.DOM.div({className: "media nested"}, - React.DOM.div({className: "video_wrapper remote_wrapper"}, - React.DOM.div({className: "video_inner remote"}) + React.createElement("div", {className: "video-layout-wrapper"}, + React.createElement("div", {className: "conversation"}, + React.createElement("div", {className: "media nested"}, + React.createElement("div", {className: "video_wrapper remote_wrapper"}, + React.createElement("div", {className: "video_inner remote"}) ), - React.DOM.div({className: localStreamClasses}) + React.createElement("div", {className: localStreamClasses}) ), - ConversationToolbar({video: this.state.video, + React.createElement(ConversationToolbar, {video: this.state.video, audio: this.state.audio, publishStream: this.publishStream, hangup: this.hangup}) @@ -346,7 +346,7 @@ /** * Notification view. */ - var NotificationView = React.createClass({displayName: 'NotificationView', + var NotificationView = React.createClass({displayName: "NotificationView", mixins: [Backbone.Events], propTypes: { @@ -357,19 +357,19 @@ render: function() { var notification = this.props.notification; return ( - React.DOM.div({className: "notificationContainer"}, - React.DOM.div({key: this.props.key, + React.createElement("div", {className: "notificationContainer"}, + React.createElement("div", {key: this.props.key, className: "alert alert-" + notification.get("level")}, - React.DOM.span({className: "message"}, notification.get("message")) + React.createElement("span", {className: "message"}, notification.get("message")) ), - React.DOM.div({className: "detailsBar details-" + notification.get("level"), + React.createElement("div", {className: "detailsBar details-" + notification.get("level"), hidden: !notification.get("details")}, - React.DOM.button({className: "detailsButton btn-info", + React.createElement("button", {className: "detailsButton btn-info", onClick: notification.get("detailsButtonCallback"), hidden: !notification.get("detailsButtonLabel") || !notification.get("detailsButtonCallback")}, notification.get("detailsButtonLabel") ), - React.DOM.span({className: "details"}, notification.get("details")) + React.createElement("span", {className: "details"}, notification.get("details")) ) ) ); @@ -379,7 +379,7 @@ /** * Notification list view. */ - var NotificationListView = React.createClass({displayName: 'NotificationListView', + var NotificationListView = React.createClass({displayName: "NotificationListView", mixins: [Backbone.Events, sharedMixins.DocumentVisibilityMixin], propTypes: { @@ -419,9 +419,9 @@ render: function() { return ( - React.DOM.div({className: "messages"}, + React.createElement("div", {className: "messages"}, this.props.notifications.map(function(notification, key) { - return NotificationView({key: key, notification: notification}); + return React.createElement(NotificationView, {key: key, notification: notification}); }) ) @@ -429,7 +429,7 @@ } }); - var Button = React.createClass({displayName: 'Button', + var Button = React.createClass({displayName: "Button", propTypes: { caption: React.PropTypes.string.isRequired, onClick: React.PropTypes.func.isRequired, @@ -453,18 +453,18 @@ classObject[this.props.additionalClass] = true; } return ( - React.DOM.button({onClick: this.props.onClick, + React.createElement("button", {onClick: this.props.onClick, disabled: this.props.disabled, id: this.props.htmlId, className: cx(classObject)}, - React.DOM.span({className: "button-caption"}, this.props.caption), + React.createElement("span", {className: "button-caption"}, this.props.caption), this.props.children ) ); } }); - var ButtonGroup = React.createClass({displayName: 'ButtonGroup', + var ButtonGroup = React.createClass({displayName: "ButtonGroup", PropTypes: { additionalClass: React.PropTypes.string }, @@ -482,7 +482,7 @@ classObject[this.props.additionalClass] = true; } return ( - React.DOM.div({className: cx(classObject)}, + React.createElement("div", {className: cx(classObject)}, this.props.children ) ); diff -Nru thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/shared/libs/react-0.11.2.js thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/shared/libs/react-0.11.2.js --- thunderbird-trunk-37.0~a1~hg20150106r17307.222089/mozilla/browser/components/loop/content/shared/libs/react-0.11.2.js 2015-01-06 13:14:20.000000000 +0000 +++ thunderbird-trunk-37.0~a1~hg20150107r17323.222415/mozilla/browser/components/loop/content/shared/libs/react-0.11.2.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,20276 +0,0 @@ -/** - * React (with addons) v0.11.2 - */ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.React=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o -1; - } - -}; - -module.exports = CSSCore; - -},{"./invariant":134}],4:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule CSSProperty - */ - -"use strict"; - -/** - * CSS properties which accept numbers but are not in units of "px". - */ -var isUnitlessNumber = { - columnCount: true, - fillOpacity: true, - flex: true, - flexGrow: true, - flexShrink: true, - fontWeight: true, - lineClamp: true, - lineHeight: true, - opacity: true, - order: true, - orphans: true, - widows: true, - zIndex: true, - zoom: true -}; - -/** - * @param {string} prefix vendor-specific prefix, eg: Webkit - * @param {string} key style name, eg: transitionDuration - * @return {string} style name prefixed with `prefix`, properly camelCased, eg: - * WebkitTransitionDuration - */ -function prefixKey(prefix, key) { - return prefix + key.charAt(0).toUpperCase() + key.substring(1); -} - -/** - * Support style names that may come passed in prefixed by adding permutations - * of vendor prefixes. - */ -var prefixes = ['Webkit', 'ms', 'Moz', 'O']; - -// Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an -// infinite loop, because it iterates over the newly added props too. -Object.keys(isUnitlessNumber).forEach(function(prop) { - prefixes.forEach(function(prefix) { - isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; - }); -}); - -/** - * Most style properties can be unset by doing .style[prop] = '' but IE8 - * doesn't like doing that with shorthand properties so for the properties that - * IE8 breaks on, which are listed here, we instead unset each of the - * individual properties. See http://bugs.jquery.com/ticket/12385. - * The 4-value 'clock' properties like margin, padding, border-width seem to - * behave without any problems. Curiously, list-style works too without any - * special prodding. - */ -var shorthandPropertyExpansions = { - background: { - backgroundImage: true, - backgroundPosition: true, - backgroundRepeat: true, - backgroundColor: true - }, - border: { - borderWidth: true, - borderStyle: true, - borderColor: true - }, - borderBottom: { - borderBottomWidth: true, - borderBottomStyle: true, - borderBottomColor: true - }, - borderLeft: { - borderLeftWidth: true, - borderLeftStyle: true, - borderLeftColor: true - }, - borderRight: { - borderRightWidth: true, - borderRightStyle: true, - borderRightColor: true - }, - borderTop: { - borderTopWidth: true, - borderTopStyle: true, - borderTopColor: true - }, - font: { - fontStyle: true, - fontVariant: true, - fontWeight: true, - fontSize: true, - lineHeight: true, - fontFamily: true - } -}; - -var CSSProperty = { - isUnitlessNumber: isUnitlessNumber, - shorthandPropertyExpansions: shorthandPropertyExpansions -}; - -module.exports = CSSProperty; - -},{}],5:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule CSSPropertyOperations - * @typechecks static-only - */ - -"use strict"; - -var CSSProperty = _dereq_("./CSSProperty"); - -var dangerousStyleValue = _dereq_("./dangerousStyleValue"); -var hyphenateStyleName = _dereq_("./hyphenateStyleName"); -var memoizeStringOnly = _dereq_("./memoizeStringOnly"); - -var processStyleName = memoizeStringOnly(function(styleName) { - return hyphenateStyleName(styleName); -}); - -/** - * Operations for dealing with CSS properties. - */ -var CSSPropertyOperations = { - - /** - * Serializes a mapping of style properties for use as inline styles: - * - * > createMarkupForStyles({width: '200px', height: 0}) - * "width:200px;height:0;" - * - * Undefined values are ignored so that declarative programming is easier. - * The result should be HTML-escaped before insertion into the DOM. - * - * @param {object} styles - * @return {?string} - */ - createMarkupForStyles: function(styles) { - var serialized = ''; - for (var styleName in styles) { - if (!styles.hasOwnProperty(styleName)) { - continue; - } - var styleValue = styles[styleName]; - if (styleValue != null) { - serialized += processStyleName(styleName) + ':'; - serialized += dangerousStyleValue(styleName, styleValue) + ';'; - } - } - return serialized || null; - }, - - /** - * Sets the value for multiple styles on a node. If a value is specified as - * '' (empty string), the corresponding style property will be unset. - * - * @param {DOMElement} node - * @param {object} styles - */ - setValueForStyles: function(node, styles) { - var style = node.style; - for (var styleName in styles) { - if (!styles.hasOwnProperty(styleName)) { - continue; - } - var styleValue = dangerousStyleValue(styleName, styles[styleName]); - if (styleValue) { - style[styleName] = styleValue; - } else { - var expansion = CSSProperty.shorthandPropertyExpansions[styleName]; - if (expansion) { - // Shorthand property that IE8 won't like unsetting, so unset each - // component to placate it - for (var individualStyleName in expansion) { - style[individualStyleName] = ''; - } - } else { - style[styleName] = ''; - } - } - } - } - -}; - -module.exports = CSSPropertyOperations; - -},{"./CSSProperty":4,"./dangerousStyleValue":115,"./hyphenateStyleName":132,"./memoizeStringOnly":143}],6:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule CallbackQueue - */ - -"use strict"; - -var PooledClass = _dereq_("./PooledClass"); - -var invariant = _dereq_("./invariant"); -var mixInto = _dereq_("./mixInto"); - -/** - * A specialized pseudo-event module to help keep track of components waiting to - * be notified when their DOM representations are available for use. - * - * This implements `PooledClass`, so you should never need to instantiate this. - * Instead, use `CallbackQueue.getPooled()`. - * - * @class ReactMountReady - * @implements PooledClass - * @internal - */ -function CallbackQueue() { - this._callbacks = null; - this._contexts = null; -} - -mixInto(CallbackQueue, { - - /** - * Enqueues a callback to be invoked when `notifyAll` is invoked. - * - * @param {function} callback Invoked when `notifyAll` is invoked. - * @param {?object} context Context to call `callback` with. - * @internal - */ - enqueue: function(callback, context) { - this._callbacks = this._callbacks || []; - this._contexts = this._contexts || []; - this._callbacks.push(callback); - this._contexts.push(context); - }, - - /** - * Invokes all enqueued callbacks and clears the queue. This is invoked after - * the DOM representation of a component has been created or updated. - * - * @internal - */ - notifyAll: function() { - var callbacks = this._callbacks; - var contexts = this._contexts; - if (callbacks) { - ("production" !== "development" ? invariant( - callbacks.length === contexts.length, - "Mismatched list of contexts in callback queue" - ) : invariant(callbacks.length === contexts.length)); - this._callbacks = null; - this._contexts = null; - for (var i = 0, l = callbacks.length; i < l; i++) { - callbacks[i].call(contexts[i]); - } - callbacks.length = 0; - contexts.length = 0; - } - }, - - /** - * Resets the internal queue. - * - * @internal - */ - reset: function() { - this._callbacks = null; - this._contexts = null; - }, - - /** - * `PooledClass` looks for this. - */ - destructor: function() { - this.reset(); - } - -}); - -PooledClass.addPoolingTo(CallbackQueue); - -module.exports = CallbackQueue; - -},{"./PooledClass":28,"./invariant":134,"./mixInto":147}],7:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule ChangeEventPlugin - */ - -"use strict"; - -var EventConstants = _dereq_("./EventConstants"); -var EventPluginHub = _dereq_("./EventPluginHub"); -var EventPropagators = _dereq_("./EventPropagators"); -var ExecutionEnvironment = _dereq_("./ExecutionEnvironment"); -var ReactUpdates = _dereq_("./ReactUpdates"); -var SyntheticEvent = _dereq_("./SyntheticEvent"); - -var isEventSupported = _dereq_("./isEventSupported"); -var isTextInputElement = _dereq_("./isTextInputElement"); -var keyOf = _dereq_("./keyOf"); - -var topLevelTypes = EventConstants.topLevelTypes; - -var eventTypes = { - change: { - phasedRegistrationNames: { - bubbled: keyOf({onChange: null}), - captured: keyOf({onChangeCapture: null}) - }, - dependencies: [ - topLevelTypes.topBlur, - topLevelTypes.topChange, - topLevelTypes.topClick, - topLevelTypes.topFocus, - topLevelTypes.topInput, - topLevelTypes.topKeyDown, - topLevelTypes.topKeyUp, - topLevelTypes.topSelectionChange - ] - } -}; - -/** - * For IE shims - */ -var activeElement = null; -var activeElementID = null; -var activeElementValue = null; -var activeElementValueProp = null; - -/** - * SECTION: handle `change` event - */ -function shouldUseChangeEvent(elem) { - return ( - elem.nodeName === 'SELECT' || - (elem.nodeName === 'INPUT' && elem.type === 'file') - ); -} - -var doesChangeEventBubble = false; -if (ExecutionEnvironment.canUseDOM) { - // See `handleChange` comment below - doesChangeEventBubble = isEventSupported('change') && ( - !('documentMode' in document) || document.documentMode > 8 - ); -} - -function manualDispatchChangeEvent(nativeEvent) { - var event = SyntheticEvent.getPooled( - eventTypes.change, - activeElementID, - nativeEvent - ); - EventPropagators.accumulateTwoPhaseDispatches(event); - - // If change and propertychange bubbled, we'd just bind to it like all the - // other events and have it go through ReactBrowserEventEmitter. Since it - // doesn't, we manually listen for the events and so we have to enqueue and - // process the abstract event manually. - // - // Batching is necessary here in order to ensure that all event handlers run - // before the next rerender (including event handlers attached to ancestor - // elements instead of directly on the input). Without this, controlled - // components don't work properly in conjunction with event bubbling because - // the component is rerendered and the value reverted before all the event - // handlers can run. See https://github.com/facebook/react/issues/708. - ReactUpdates.batchedUpdates(runEventInBatch, event); -} - -function runEventInBatch(event) { - EventPluginHub.enqueueEvents(event); - EventPluginHub.processEventQueue(); -} - -function startWatchingForChangeEventIE8(target, targetID) { - activeElement = target; - activeElementID = targetID; - activeElement.attachEvent('onchange', manualDispatchChangeEvent); -} - -function stopWatchingForChangeEventIE8() { - if (!activeElement) { - return; - } - activeElement.detachEvent('onchange', manualDispatchChangeEvent); - activeElement = null; - activeElementID = null; -} - -function getTargetIDForChangeEvent( - topLevelType, - topLevelTarget, - topLevelTargetID) { - if (topLevelType === topLevelTypes.topChange) { - return topLevelTargetID; - } -} -function handleEventsForChangeEventIE8( - topLevelType, - topLevelTarget, - topLevelTargetID) { - if (topLevelType === topLevelTypes.topFocus) { - // stopWatching() should be a noop here but we call it just in case we - // missed a blur event somehow. - stopWatchingForChangeEventIE8(); - startWatchingForChangeEventIE8(topLevelTarget, topLevelTargetID); - } else if (topLevelType === topLevelTypes.topBlur) { - stopWatchingForChangeEventIE8(); - } -} - - -/** - * SECTION: handle `input` event - */ -var isInputEventSupported = false; -if (ExecutionEnvironment.canUseDOM) { - // IE9 claims to support the input event but fails to trigger it when - // deleting text, so we ignore its input events - isInputEventSupported = isEventSupported('input') && ( - !('documentMode' in document) || document.documentMode > 9 - ); -} - -/** - * (For old IE.) Replacement getter/setter for the `value` property that gets - * set on the active element. - */ -var newValueProp = { - get: function() { - return activeElementValueProp.get.call(this); - }, - set: function(val) { - // Cast to a string so we can do equality checks. - activeElementValue = '' + val; - activeElementValueProp.set.call(this, val); - } -}; - -/** - * (For old IE.) Starts tracking propertychange events on the passed-in element - * and override the value property so that we can distinguish user events from - * value changes in JS. - */ -function startWatchingForValueChange(target, targetID) { - activeElement = target; - activeElementID = targetID; - activeElementValue = target.value; - activeElementValueProp = Object.getOwnPropertyDescriptor( - target.constructor.prototype, - 'value' - ); - - Object.defineProperty(activeElement, 'value', newValueProp); - activeElement.attachEvent('onpropertychange', handlePropertyChange); -} - -/** - * (For old IE.) Removes the event listeners from the currently-tracked element, - * if any exists. - */ -function stopWatchingForValueChange() { - if (!activeElement) { - return; - } - - // delete restores the original property definition - delete activeElement.value; - activeElement.detachEvent('onpropertychange', handlePropertyChange); - - activeElement = null; - activeElementID = null; - activeElementValue = null; - activeElementValueProp = null; -} - -/** - * (For old IE.) Handles a propertychange event, sending a `change` event if - * the value of the active element has changed. - */ -function handlePropertyChange(nativeEvent) { - if (nativeEvent.propertyName !== 'value') { - return; - } - var value = nativeEvent.srcElement.value; - if (value === activeElementValue) { - return; - } - activeElementValue = value; - - manualDispatchChangeEvent(nativeEvent); -} - -/** - * If a `change` event should be fired, returns the target's ID. - */ -function getTargetIDForInputEvent( - topLevelType, - topLevelTarget, - topLevelTargetID) { - if (topLevelType === topLevelTypes.topInput) { - // In modern browsers (i.e., not IE8 or IE9), the input event is exactly - // what we want so fall through here and trigger an abstract event - return topLevelTargetID; - } -} - -// For IE8 and IE9. -function handleEventsForInputEventIE( - topLevelType, - topLevelTarget, - topLevelTargetID) { - if (topLevelType === topLevelTypes.topFocus) { - // In IE8, we can capture almost all .value changes by adding a - // propertychange handler and looking for events with propertyName - // equal to 'value' - // In IE9, propertychange fires for most input events but is buggy and - // doesn't fire when text is deleted, but conveniently, selectionchange - // appears to fire in all of the remaining cases so we catch those and - // forward the event if the value has changed - // In either case, we don't want to call the event handler if the value - // is changed from JS so we redefine a setter for `.value` that updates - // our activeElementValue variable, allowing us to ignore those changes - // - // stopWatching() should be a noop here but we call it just in case we - // missed a blur event somehow. - stopWatchingForValueChange(); - startWatchingForValueChange(topLevelTarget, topLevelTargetID); - } else if (topLevelType === topLevelTypes.topBlur) { - stopWatchingForValueChange(); - } -} - -// For IE8 and IE9. -function getTargetIDForInputEventIE( - topLevelType, - topLevelTarget, - topLevelTargetID) { - if (topLevelType === topLevelTypes.topSelectionChange || - topLevelType === topLevelTypes.topKeyUp || - topLevelType === topLevelTypes.topKeyDown) { - // On the selectionchange event, the target is just document which isn't - // helpful for us so just check activeElement instead. - // - // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire - // propertychange on the first input event after setting `value` from a - // script and fires only keydown, keypress, keyup. Catching keyup usually - // gets it and catching keydown lets us fire an event for the first - // keystroke if user does a key repeat (it'll be a little delayed: right - // before the second keystroke). Other input methods (e.g., paste) seem to - // fire selectionchange normally. - if (activeElement && activeElement.value !== activeElementValue) { - activeElementValue = activeElement.value; - return activeElementID; - } - } -} - - -/** - * SECTION: handle `click` event - */ -function shouldUseClickEvent(elem) { - // Use the `click` event to detect changes to checkbox and radio inputs. - // This approach works across all browsers, whereas `change` does not fire - // until `blur` in IE8. - return ( - elem.nodeName === 'INPUT' && - (elem.type === 'checkbox' || elem.type === 'radio') - ); -} - -function getTargetIDForClickEvent( - topLevelType, - topLevelTarget, - topLevelTargetID) { - if (topLevelType === topLevelTypes.topClick) { - return topLevelTargetID; - } -} - -/** - * This plugin creates an `onChange` event that normalizes change events - * across form elements. This event fires at a time when it's possible to - * change the element's value without seeing a flicker. - * - * Supported elements are: - * - input (see `isTextInputElement`) - * - textarea - * - select - */ -var ChangeEventPlugin = { - - eventTypes: eventTypes, - - /** - * @param {string} topLevelType Record from `EventConstants`. - * @param {DOMEventTarget} topLevelTarget The listening component root node. - * @param {string} topLevelTargetID ID of `topLevelTarget`. - * @param {object} nativeEvent Native browser event. - * @return {*} An accumulation of synthetic events. - * @see {EventPluginHub.extractEvents} - */ - extractEvents: function( - topLevelType, - topLevelTarget, - topLevelTargetID, - nativeEvent) { - - var getTargetIDFunc, handleEventFunc; - if (shouldUseChangeEvent(topLevelTarget)) { - if (doesChangeEventBubble) { - getTargetIDFunc = getTargetIDForChangeEvent; - } else { - handleEventFunc = handleEventsForChangeEventIE8; - } - } else if (isTextInputElement(topLevelTarget)) { - if (isInputEventSupported) { - getTargetIDFunc = getTargetIDForInputEvent; - } else { - getTargetIDFunc = getTargetIDForInputEventIE; - handleEventFunc = handleEventsForInputEventIE; - } - } else if (shouldUseClickEvent(topLevelTarget)) { - getTargetIDFunc = getTargetIDForClickEvent; - } - - if (getTargetIDFunc) { - var targetID = getTargetIDFunc( - topLevelType, - topLevelTarget, - topLevelTargetID - ); - if (targetID) { - var event = SyntheticEvent.getPooled( - eventTypes.change, - targetID, - nativeEvent - ); - EventPropagators.accumulateTwoPhaseDispatches(event); - return event; - } - } - - if (handleEventFunc) { - handleEventFunc( - topLevelType, - topLevelTarget, - topLevelTargetID - ); - } - } - -}; - -module.exports = ChangeEventPlugin; - -},{"./EventConstants":16,"./EventPluginHub":18,"./EventPropagators":21,"./ExecutionEnvironment":22,"./ReactUpdates":87,"./SyntheticEvent":96,"./isEventSupported":135,"./isTextInputElement":137,"./keyOf":141}],8:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule ClientReactRootIndex - * @typechecks - */ - -"use strict"; - -var nextReactRootIndex = 0; - -var ClientReactRootIndex = { - createReactRootIndex: function() { - return nextReactRootIndex++; - } -}; - -module.exports = ClientReactRootIndex; - -},{}],9:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule CompositionEventPlugin - * @typechecks static-only - */ - -"use strict"; - -var EventConstants = _dereq_("./EventConstants"); -var EventPropagators = _dereq_("./EventPropagators"); -var ExecutionEnvironment = _dereq_("./ExecutionEnvironment"); -var ReactInputSelection = _dereq_("./ReactInputSelection"); -var SyntheticCompositionEvent = _dereq_("./SyntheticCompositionEvent"); - -var getTextContentAccessor = _dereq_("./getTextContentAccessor"); -var keyOf = _dereq_("./keyOf"); - -var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space -var START_KEYCODE = 229; - -var useCompositionEvent = ( - ExecutionEnvironment.canUseDOM && - 'CompositionEvent' in window -); - -// In IE9+, we have access to composition events, but the data supplied -// by the native compositionend event may be incorrect. In Korean, for example, -// the compositionend event contains only one character regardless of -// how many characters have been composed since compositionstart. -// We therefore use the fallback data while still using the native -// events as triggers. -var useFallbackData = ( - !useCompositionEvent || - ( - 'documentMode' in document && - document.documentMode > 8 && - document.documentMode <= 11 - ) -); - -var topLevelTypes = EventConstants.topLevelTypes; -var currentComposition = null; - -// Events and their corresponding property names. -var eventTypes = { - compositionEnd: { - phasedRegistrationNames: { - bubbled: keyOf({onCompositionEnd: null}), - captured: keyOf({onCompositionEndCapture: null}) - }, - dependencies: [ - topLevelTypes.topBlur, - topLevelTypes.topCompositionEnd, - topLevelTypes.topKeyDown, - topLevelTypes.topKeyPress, - topLevelTypes.topKeyUp, - topLevelTypes.topMouseDown - ] - }, - compositionStart: { - phasedRegistrationNames: { - bubbled: keyOf({onCompositionStart: null}), - captured: keyOf({onCompositionStartCapture: null}) - }, - dependencies: [ - topLevelTypes.topBlur, - topLevelTypes.topCompositionStart, - topLevelTypes.topKeyDown, - topLevelTypes.topKeyPress, - topLevelTypes.topKeyUp, - topLevelTypes.topMouseDown - ] - }, - compositionUpdate: { - phasedRegistrationNames: { - bubbled: keyOf({onCompositionUpdate: null}), - captured: keyOf({onCompositionUpdateCapture: null}) - }, - dependencies: [ - topLevelTypes.topBlur, - topLevelTypes.topCompositionUpdate, - topLevelTypes.topKeyDown, - topLevelTypes.topKeyPress, - topLevelTypes.topKeyUp, - topLevelTypes.topMouseDown - ] - } -}; - -/** - * Translate native top level events into event types. - * - * @param {string} topLevelType - * @return {object} - */ -function getCompositionEventType(topLevelType) { - switch (topLevelType) { - case topLevelTypes.topCompositionStart: - return eventTypes.compositionStart; - case topLevelTypes.topCompositionEnd: - return eventTypes.compositionEnd; - case topLevelTypes.topCompositionUpdate: - return eventTypes.compositionUpdate; - } -} - -/** - * Does our fallback best-guess model think this event signifies that - * composition has begun? - * - * @param {string} topLevelType - * @param {object} nativeEvent - * @return {boolean} - */ -function isFallbackStart(topLevelType, nativeEvent) { - return ( - topLevelType === topLevelTypes.topKeyDown && - nativeEvent.keyCode === START_KEYCODE - ); -} - -/** - * Does our fallback mode think that this event is the end of composition? - * - * @param {string} topLevelType - * @param {object} nativeEvent - * @return {boolean} - */ -function isFallbackEnd(topLevelType, nativeEvent) { - switch (topLevelType) { - case topLevelTypes.topKeyUp: - // Command keys insert or clear IME input. - return (END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1); - case topLevelTypes.topKeyDown: - // Expect IME keyCode on each keydown. If we get any other - // code we must have exited earlier. - return (nativeEvent.keyCode !== START_KEYCODE); - case topLevelTypes.topKeyPress: - case topLevelTypes.topMouseDown: - case topLevelTypes.topBlur: - // Events are not possible without cancelling IME. - return true; - default: - return false; - } -} - -/** - * Helper class stores information about selection and document state - * so we can figure out what changed at a later date. - * - * @param {DOMEventTarget} root - */ -function FallbackCompositionState(root) { - this.root = root; - this.startSelection = ReactInputSelection.getSelection(root); - this.startValue = this.getText(); -} - -/** - * Get current text of input. - * - * @return {string} - */ -FallbackCompositionState.prototype.getText = function() { - return this.root.value || this.root[getTextContentAccessor()]; -}; - -/** - * Text that has changed since the start of composition. - * - * @return {string} - */ -FallbackCompositionState.prototype.getData = function() { - var endValue = this.getText(); - var prefixLength = this.startSelection.start; - var suffixLength = this.startValue.length - this.startSelection.end; - - return endValue.substr( - prefixLength, - endValue.length - suffixLength - prefixLength - ); -}; - -/** - * This plugin creates `onCompositionStart`, `onCompositionUpdate` and - * `onCompositionEnd` events on inputs, textareas and contentEditable - * nodes. - */ -var CompositionEventPlugin = { - - eventTypes: eventTypes, - - /** - * @param {string} topLevelType Record from `EventConstants`. - * @param {DOMEventTarget} topLevelTarget The listening component root node. - * @param {string} topLevelTargetID ID of `topLevelTarget`. - * @param {object} nativeEvent Native browser event. - * @return {*} An accumulation of synthetic events. - * @see {EventPluginHub.extractEvents} - */ - extractEvents: function( - topLevelType, - topLevelTarget, - topLevelTargetID, - nativeEvent) { - - var eventType; - var data; - - if (useCompositionEvent) { - eventType = getCompositionEventType(topLevelType); - } else if (!currentComposition) { - if (isFallbackStart(topLevelType, nativeEvent)) { - eventType = eventTypes.compositionStart; - } - } else if (isFallbackEnd(topLevelType, nativeEvent)) { - eventType = eventTypes.compositionEnd; - } - - if (useFallbackData) { - // The current composition is stored statically and must not be - // overwritten while composition continues. - if (!currentComposition && eventType === eventTypes.compositionStart) { - currentComposition = new FallbackCompositionState(topLevelTarget); - } else if (eventType === eventTypes.compositionEnd) { - if (currentComposition) { - data = currentComposition.getData(); - currentComposition = null; - } - } - } - - if (eventType) { - var event = SyntheticCompositionEvent.getPooled( - eventType, - topLevelTargetID, - nativeEvent - ); - if (data) { - // Inject data generated from fallback path into the synthetic event. - // This matches the property of native CompositionEventInterface. - event.data = data; - } - EventPropagators.accumulateTwoPhaseDispatches(event); - return event; - } - } -}; - -module.exports = CompositionEventPlugin; - -},{"./EventConstants":16,"./EventPropagators":21,"./ExecutionEnvironment":22,"./ReactInputSelection":63,"./SyntheticCompositionEvent":94,"./getTextContentAccessor":129,"./keyOf":141}],10:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule DOMChildrenOperations - * @typechecks static-only - */ - -"use strict"; - -var Danger = _dereq_("./Danger"); -var ReactMultiChildUpdateTypes = _dereq_("./ReactMultiChildUpdateTypes"); - -var getTextContentAccessor = _dereq_("./getTextContentAccessor"); -var invariant = _dereq_("./invariant"); - -/** - * The DOM property to use when setting text content. - * - * @type {string} - * @private - */ -var textContentAccessor = getTextContentAccessor(); - -/** - * Inserts `childNode` as a child of `parentNode` at the `index`. - * - * @param {DOMElement} parentNode Parent node in which to insert. - * @param {DOMElement} childNode Child node to insert. - * @param {number} index Index at which to insert the child. - * @internal - */ -function insertChildAt(parentNode, childNode, index) { - // By exploiting arrays returning `undefined` for an undefined index, we can - // rely exclusively on `insertBefore(node, null)` instead of also using - // `appendChild(node)`. However, using `undefined` is not allowed by all - // browsers so we must replace it with `null`. - parentNode.insertBefore( - childNode, - parentNode.childNodes[index] || null - ); -} - -var updateTextContent; -if (textContentAccessor === 'textContent') { - /** - * Sets the text content of `node` to `text`. - * - * @param {DOMElement} node Node to change - * @param {string} text New text content - */ - updateTextContent = function(node, text) { - node.textContent = text; - }; -} else { - /** - * Sets the text content of `node` to `text`. - * - * @param {DOMElement} node Node to change - * @param {string} text New text content - */ - updateTextContent = function(node, text) { - // In order to preserve newlines correctly, we can't use .innerText to set - // the contents (see #1080), so we empty the element then append a text node - while (node.firstChild) { - node.removeChild(node.firstChild); - } - if (text) { - var doc = node.ownerDocument || document; - node.appendChild(doc.createTextNode(text)); - } - }; -} - -/** - * Operations for updating with DOM children. - */ -var DOMChildrenOperations = { - - dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup, - - updateTextContent: updateTextContent, - - /** - * Updates a component's children by processing a series of updates. The - * update configurations are each expected to have a `parentNode` property. - * - * @param {array} updates List of update configurations. - * @param {array} markupList List of markup strings. - * @internal - */ - processUpdates: function(updates, markupList) { - var update; - // Mapping from parent IDs to initial child orderings. - var initialChildren = null; - // List of children that will be moved or removed. - var updatedChildren = null; - - for (var i = 0; update = updates[i]; i++) { - if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING || - update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) { - var updatedIndex = update.fromIndex; - var updatedChild = update.parentNode.childNodes[updatedIndex]; - var parentID = update.parentID; - - ("production" !== "development" ? invariant( - updatedChild, - 'processUpdates(): Unable to find child %s of element. This ' + - 'probably means the DOM was unexpectedly mutated (e.g., by the ' + - 'browser), usually due to forgetting a when using tables, ' + - 'nesting

or tags, or using non-SVG elements in an '+ - 'parent. Try inspecting the child nodes of the element with React ' + - 'ID `%s`.', - updatedIndex, - parentID - ) : invariant(updatedChild)); - - initialChildren = initialChildren || {}; - initialChildren[parentID] = initialChildren[parentID] || []; - initialChildren[parentID][updatedIndex] = updatedChild; - - updatedChildren = updatedChildren || []; - updatedChildren.push(updatedChild); - } - } - - var renderedMarkup = Danger.dangerouslyRenderMarkup(markupList); - - // Remove updated children first so that `toIndex` is consistent. - if (updatedChildren) { - for (var j = 0; j < updatedChildren.length; j++) { - updatedChildren[j].parentNode.removeChild(updatedChildren[j]); - } - } - - for (var k = 0; update = updates[k]; k++) { - switch (update.type) { - case ReactMultiChildUpdateTypes.INSERT_MARKUP: - insertChildAt( - update.parentNode, - renderedMarkup[update.markupIndex], - update.toIndex - ); - break; - case ReactMultiChildUpdateTypes.MOVE_EXISTING: - insertChildAt( - update.parentNode, - initialChildren[update.parentID][update.fromIndex], - update.toIndex - ); - break; - case ReactMultiChildUpdateTypes.TEXT_CONTENT: - updateTextContent( - update.parentNode, - update.textContent - ); - break; - case ReactMultiChildUpdateTypes.REMOVE_NODE: - // Already removed by the for-loop above. - break; - } - } - } - -}; - -module.exports = DOMChildrenOperations; - -},{"./Danger":13,"./ReactMultiChildUpdateTypes":69,"./getTextContentAccessor":129,"./invariant":134}],11:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule DOMProperty - * @typechecks static-only - */ - -/*jslint bitwise: true */ - -"use strict"; - -var invariant = _dereq_("./invariant"); - -var DOMPropertyInjection = { - /** - * Mapping from normalized, camelcased property names to a configuration that - * specifies how the associated DOM property should be accessed or rendered. - */ - MUST_USE_ATTRIBUTE: 0x1, - MUST_USE_PROPERTY: 0x2, - HAS_SIDE_EFFECTS: 0x4, - HAS_BOOLEAN_VALUE: 0x8, - HAS_NUMERIC_VALUE: 0x10, - HAS_POSITIVE_NUMERIC_VALUE: 0x20 | 0x10, - HAS_OVERLOADED_BOOLEAN_VALUE: 0x40, - - /** - * Inject some specialized knowledge about the DOM. This takes a config object - * with the following properties: - * - * isCustomAttribute: function that given an attribute name will return true - * if it can be inserted into the DOM verbatim. Useful for data-* or aria-* - * attributes where it's impossible to enumerate all of the possible - * attribute names, - * - * Properties: object mapping DOM property name to one of the - * DOMPropertyInjection constants or null. If your attribute isn't in here, - * it won't get written to the DOM. - * - * DOMAttributeNames: object mapping React attribute name to the DOM - * attribute name. Attribute names not specified use the **lowercase** - * normalized name. - * - * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties. - * Property names not specified use the normalized name. - * - * DOMMutationMethods: Properties that require special mutation methods. If - * `value` is undefined, the mutation method should unset the property. - * - * @param {object} domPropertyConfig the config as described above. - */ - injectDOMPropertyConfig: function(domPropertyConfig) { - var Properties = domPropertyConfig.Properties || {}; - var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {}; - var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {}; - var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {}; - - if (domPropertyConfig.isCustomAttribute) { - DOMProperty._isCustomAttributeFunctions.push( - domPropertyConfig.isCustomAttribute - ); - } - - for (var propName in Properties) { - ("production" !== "development" ? invariant( - !DOMProperty.isStandardName.hasOwnProperty(propName), - 'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' + - '\'%s\' which has already been injected. You may be accidentally ' + - 'injecting the same DOM property config twice, or you may be ' + - 'injecting two configs that have conflicting property names.', - propName - ) : invariant(!DOMProperty.isStandardName.hasOwnProperty(propName))); - - DOMProperty.isStandardName[propName] = true; - - var lowerCased = propName.toLowerCase(); - DOMProperty.getPossibleStandardName[lowerCased] = propName; - - if (DOMAttributeNames.hasOwnProperty(propName)) { - var attributeName = DOMAttributeNames[propName]; - DOMProperty.getPossibleStandardName[attributeName] = propName; - DOMProperty.getAttributeName[propName] = attributeName; - } else { - DOMProperty.getAttributeName[propName] = lowerCased; - } - - DOMProperty.getPropertyName[propName] = - DOMPropertyNames.hasOwnProperty(propName) ? - DOMPropertyNames[propName] : - propName; - - if (DOMMutationMethods.hasOwnProperty(propName)) { - DOMProperty.getMutationMethod[propName] = DOMMutationMethods[propName]; - } else { - DOMProperty.getMutationMethod[propName] = null; - } - - var propConfig = Properties[propName]; - DOMProperty.mustUseAttribute[propName] = - propConfig & DOMPropertyInjection.MUST_USE_ATTRIBUTE; - DOMProperty.mustUseProperty[propName] = - propConfig & DOMPropertyInjection.MUST_USE_PROPERTY; - DOMProperty.hasSideEffects[propName] = - propConfig & DOMPropertyInjection.HAS_SIDE_EFFECTS; - DOMProperty.hasBooleanValue[propName] = - propConfig & DOMPropertyInjection.HAS_BOOLEAN_VALUE; - DOMProperty.hasNumericValue[propName] = - propConfig & DOMPropertyInjection.HAS_NUMERIC_VALUE; - DOMProperty.hasPositiveNumericValue[propName] = - propConfig & DOMPropertyInjection.HAS_POSITIVE_NUMERIC_VALUE; - DOMProperty.hasOverloadedBooleanValue[propName] = - propConfig & DOMPropertyInjection.HAS_OVERLOADED_BOOLEAN_VALUE; - - ("production" !== "development" ? invariant( - !DOMProperty.mustUseAttribute[propName] || - !DOMProperty.mustUseProperty[propName], - 'DOMProperty: Cannot require using both attribute and property: %s', - propName - ) : invariant(!DOMProperty.mustUseAttribute[propName] || - !DOMProperty.mustUseProperty[propName])); - ("production" !== "development" ? invariant( - DOMProperty.mustUseProperty[propName] || - !DOMProperty.hasSideEffects[propName], - 'DOMProperty: Properties that have side effects must use property: %s', - propName - ) : invariant(DOMProperty.mustUseProperty[propName] || - !DOMProperty.hasSideEffects[propName])); - ("production" !== "development" ? invariant( - !!DOMProperty.hasBooleanValue[propName] + - !!DOMProperty.hasNumericValue[propName] + - !!DOMProperty.hasOverloadedBooleanValue[propName] <= 1, - 'DOMProperty: Value can be one of boolean, overloaded boolean, or ' + - 'numeric value, but not a combination: %s', - propName - ) : invariant(!!DOMProperty.hasBooleanValue[propName] + - !!DOMProperty.hasNumericValue[propName] + - !!DOMProperty.hasOverloadedBooleanValue[propName] <= 1)); - } - } -}; -var defaultValueCache = {}; - -/** - * DOMProperty exports lookup objects that can be used like functions: - * - * > DOMProperty.isValid['id'] - * true - * > DOMProperty.isValid['foobar'] - * undefined - * - * Although this may be confusing, it performs better in general. - * - * @see http://jsperf.com/key-exists - * @see http://jsperf.com/key-missing - */ -var DOMProperty = { - - ID_ATTRIBUTE_NAME: 'data-reactid', - - /** - * Checks whether a property name is a standard property. - * @type {Object} - */ - isStandardName: {}, - - /** - * Mapping from lowercase property names to the properly cased version, used - * to warn in the case of missing properties. - * @type {Object} - */ - getPossibleStandardName: {}, - - /** - * Mapping from normalized names to attribute names that differ. Attribute - * names are used when rendering markup or with `*Attribute()`. - * @type {Object} - */ - getAttributeName: {}, - - /** - * Mapping from normalized names to properties on DOM node instances. - * (This includes properties that mutate due to external factors.) - * @type {Object} - */ - getPropertyName: {}, - - /** - * Mapping from normalized names to mutation methods. This will only exist if - * mutation cannot be set simply by the property or `setAttribute()`. - * @type {Object} - */ - getMutationMethod: {}, - - /** - * Whether the property must be accessed and mutated as an object property. - * @type {Object} - */ - mustUseAttribute: {}, - - /** - * Whether the property must be accessed and mutated using `*Attribute()`. - * (This includes anything that fails ` in `.) - * @type {Object} - */ - mustUseProperty: {}, - - /** - * Whether or not setting a value causes side effects such as triggering - * resources to be loaded or text selection changes. We must ensure that - * the value is only set if it has changed. - * @type {Object} - */ - hasSideEffects: {}, - - /** - * Whether the property should be removed when set to a falsey value. - * @type {Object} - */ - hasBooleanValue: {}, - - /** - * Whether the property must be numeric or parse as a - * numeric and should be removed when set to a falsey value. - * @type {Object} - */ - hasNumericValue: {}, - - /** - * Whether the property must be positive numeric or parse as a positive - * numeric and should be removed when set to a falsey value. - * @type {Object} - */ - hasPositiveNumericValue: {}, - - /** - * Whether the property can be used as a flag as well as with a value. Removed - * when strictly equal to false; present without a value when strictly equal - * to true; present with a value otherwise. - * @type {Object} - */ - hasOverloadedBooleanValue: {}, - - /** - * All of the isCustomAttribute() functions that have been injected. - */ - _isCustomAttributeFunctions: [], - - /** - * Checks whether a property name is a custom attribute. - * @method - */ - isCustomAttribute: function(attributeName) { - for (var i = 0; i < DOMProperty._isCustomAttributeFunctions.length; i++) { - var isCustomAttributeFn = DOMProperty._isCustomAttributeFunctions[i]; - if (isCustomAttributeFn(attributeName)) { - return true; - } - } - return false; - }, - - /** - * Returns the default property value for a DOM property (i.e., not an - * attribute). Most default values are '' or false, but not all. Worse yet, - * some (in particular, `type`) vary depending on the type of element. - * - * TODO: Is it better to grab all the possible properties when creating an - * element to avoid having to create the same element twice? - */ - getDefaultValueForProperty: function(nodeName, prop) { - var nodeDefaults = defaultValueCache[nodeName]; - var testElement; - if (!nodeDefaults) { - defaultValueCache[nodeName] = nodeDefaults = {}; - } - if (!(prop in nodeDefaults)) { - testElement = document.createElement(nodeName); - nodeDefaults[prop] = testElement[prop]; - } - return nodeDefaults[prop]; - }, - - injection: DOMPropertyInjection -}; - -module.exports = DOMProperty; - -},{"./invariant":134}],12:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule DOMPropertyOperations - * @typechecks static-only - */ - -"use strict"; - -var DOMProperty = _dereq_("./DOMProperty"); - -var escapeTextForBrowser = _dereq_("./escapeTextForBrowser"); -var memoizeStringOnly = _dereq_("./memoizeStringOnly"); -var warning = _dereq_("./warning"); - -function shouldIgnoreValue(name, value) { - return value == null || - (DOMProperty.hasBooleanValue[name] && !value) || - (DOMProperty.hasNumericValue[name] && isNaN(value)) || - (DOMProperty.hasPositiveNumericValue[name] && (value < 1)) || - (DOMProperty.hasOverloadedBooleanValue[name] && value === false); -} - -var processAttributeNameAndPrefix = memoizeStringOnly(function(name) { - return escapeTextForBrowser(name) + '="'; -}); - -if ("production" !== "development") { - var reactProps = { - children: true, - dangerouslySetInnerHTML: true, - key: true, - ref: true - }; - var warnedProperties = {}; - - var warnUnknownProperty = function(name) { - if (reactProps.hasOwnProperty(name) && reactProps[name] || - warnedProperties.hasOwnProperty(name) && warnedProperties[name]) { - return; - } - - warnedProperties[name] = true; - var lowerCasedName = name.toLowerCase(); - - // data-* attributes should be lowercase; suggest the lowercase version - var standardName = ( - DOMProperty.isCustomAttribute(lowerCasedName) ? - lowerCasedName : - DOMProperty.getPossibleStandardName.hasOwnProperty(lowerCasedName) ? - DOMProperty.getPossibleStandardName[lowerCasedName] : - null - ); - - // For now, only warn when we have a suggested correction. This prevents - // logging too much when using transferPropsTo. - ("production" !== "development" ? warning( - standardName == null, - 'Unknown DOM property ' + name + '. Did you mean ' + standardName + '?' - ) : null); - - }; -} - -/** - * Operations for dealing with DOM properties. - */ -var DOMPropertyOperations = { - - /** - * Creates markup for the ID property. - * - * @param {string} id Unescaped ID. - * @return {string} Markup string. - */ - createMarkupForID: function(id) { - return processAttributeNameAndPrefix(DOMProperty.ID_ATTRIBUTE_NAME) + - escapeTextForBrowser(id) + '"'; - }, - - /** - * Creates markup for a property. - * - * @param {string} name - * @param {*} value - * @return {?string} Markup string, or null if the property was invalid. - */ - createMarkupForProperty: function(name, value) { - if (DOMProperty.isStandardName.hasOwnProperty(name) && - DOMProperty.isStandardName[name]) { - if (shouldIgnoreValue(name, value)) { - return ''; - } - var attributeName = DOMProperty.getAttributeName[name]; - if (DOMProperty.hasBooleanValue[name] || - (DOMProperty.hasOverloadedBooleanValue[name] && value === true)) { - return escapeTextForBrowser(attributeName); - } - return processAttributeNameAndPrefix(attributeName) + - escapeTextForBrowser(value) + '"'; - } else if (DOMProperty.isCustomAttribute(name)) { - if (value == null) { - return ''; - } - return processAttributeNameAndPrefix(name) + - escapeTextForBrowser(value) + '"'; - } else if ("production" !== "development") { - warnUnknownProperty(name); - } - return null; - }, - - /** - * Sets the value for a property on a node. - * - * @param {DOMElement} node - * @param {string} name - * @param {*} value - */ - setValueForProperty: function(node, name, value) { - if (DOMProperty.isStandardName.hasOwnProperty(name) && - DOMProperty.isStandardName[name]) { - var mutationMethod = DOMProperty.getMutationMethod[name]; - if (mutationMethod) { - mutationMethod(node, value); - } else if (shouldIgnoreValue(name, value)) { - this.deleteValueForProperty(node, name); - } else if (DOMProperty.mustUseAttribute[name]) { - node.setAttribute(DOMProperty.getAttributeName[name], '' + value); - } else { - var propName = DOMProperty.getPropertyName[name]; - if (!DOMProperty.hasSideEffects[name] || node[propName] !== value) { - node[propName] = value; - } - } - } else if (DOMProperty.isCustomAttribute(name)) { - if (value == null) { - node.removeAttribute(name); - } else { - node.setAttribute(name, '' + value); - } - } else if ("production" !== "development") { - warnUnknownProperty(name); - } - }, - - /** - * Deletes the value for a property on a node. - * - * @param {DOMElement} node - * @param {string} name - */ - deleteValueForProperty: function(node, name) { - if (DOMProperty.isStandardName.hasOwnProperty(name) && - DOMProperty.isStandardName[name]) { - var mutationMethod = DOMProperty.getMutationMethod[name]; - if (mutationMethod) { - mutationMethod(node, undefined); - } else if (DOMProperty.mustUseAttribute[name]) { - node.removeAttribute(DOMProperty.getAttributeName[name]); - } else { - var propName = DOMProperty.getPropertyName[name]; - var defaultValue = DOMProperty.getDefaultValueForProperty( - node.nodeName, - propName - ); - if (!DOMProperty.hasSideEffects[name] || - node[propName] !== defaultValue) { - node[propName] = defaultValue; - } - } - } else if (DOMProperty.isCustomAttribute(name)) { - node.removeAttribute(name); - } else if ("production" !== "development") { - warnUnknownProperty(name); - } - } - -}; - -module.exports = DOMPropertyOperations; - -},{"./DOMProperty":11,"./escapeTextForBrowser":118,"./memoizeStringOnly":143,"./warning":158}],13:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule Danger - * @typechecks static-only - */ - -/*jslint evil: true, sub: true */ - -"use strict"; - -var ExecutionEnvironment = _dereq_("./ExecutionEnvironment"); - -var createNodesFromMarkup = _dereq_("./createNodesFromMarkup"); -var emptyFunction = _dereq_("./emptyFunction"); -var getMarkupWrap = _dereq_("./getMarkupWrap"); -var invariant = _dereq_("./invariant"); - -var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/; -var RESULT_INDEX_ATTR = 'data-danger-index'; - -/** - * Extracts the `nodeName` from a string of markup. - * - * NOTE: Extracting the `nodeName` does not require a regular expression match - * because we make assumptions about React-generated markup (i.e. there are no - * spaces surrounding the opening tag and there is at least one attribute). - * - * @param {string} markup String of markup. - * @return {string} Node name of the supplied markup. - * @see http://jsperf.com/extract-nodename - */ -function getNodeName(markup) { - return markup.substring(1, markup.indexOf(' ')); -} - -var Danger = { - - /** - * Renders markup into an array of nodes. The markup is expected to render - * into a list of root nodes. Also, the length of `resultList` and - * `markupList` should be the same. - * - * @param {array} markupList List of markup strings to render. - * @return {array} List of rendered nodes. - * @internal - */ - dangerouslyRenderMarkup: function(markupList) { - ("production" !== "development" ? invariant( - ExecutionEnvironment.canUseDOM, - 'dangerouslyRenderMarkup(...): Cannot render markup in a Worker ' + - 'thread. This is likely a bug in the framework. Please report ' + - 'immediately.' - ) : invariant(ExecutionEnvironment.canUseDOM)); - var nodeName; - var markupByNodeName = {}; - // Group markup by `nodeName` if a wrap is necessary, else by '*'. - for (var i = 0; i < markupList.length; i++) { - ("production" !== "development" ? invariant( - markupList[i], - 'dangerouslyRenderMarkup(...): Missing markup.' - ) : invariant(markupList[i])); - nodeName = getNodeName(markupList[i]); - nodeName = getMarkupWrap(nodeName) ? nodeName : '*'; - markupByNodeName[nodeName] = markupByNodeName[nodeName] || []; - markupByNodeName[nodeName][i] = markupList[i]; - } - var resultList = []; - var resultListAssignmentCount = 0; - for (nodeName in markupByNodeName) { - if (!markupByNodeName.hasOwnProperty(nodeName)) { - continue; - } - var markupListByNodeName = markupByNodeName[nodeName]; - - // This for-in loop skips the holes of the sparse array. The order of - // iteration should follow the order of assignment, which happens to match - // numerical index order, but we don't rely on that. - for (var resultIndex in markupListByNodeName) { - if (markupListByNodeName.hasOwnProperty(resultIndex)) { - var markup = markupListByNodeName[resultIndex]; - - // Push the requested markup with an additional RESULT_INDEX_ATTR - // attribute. If the markup does not start with a < character, it - // will be discarded below (with an appropriate console.error). - markupListByNodeName[resultIndex] = markup.replace( - OPEN_TAG_NAME_EXP, - // This index will be parsed back out below. - '$1 ' + RESULT_INDEX_ATTR + '="' + resultIndex + '" ' - ); - } - } - - // Render each group of markup with similar wrapping `nodeName`. - var renderNodes = createNodesFromMarkup( - markupListByNodeName.join(''), - emptyFunction // Do nothing special with

; - * } - * }); - * - * Note: This only checks shallow equality for props and state. If these contain - * complex data structures this mixin may have false-negatives for deeper - * differences. Only mixin to components which have simple props and state, or - * use `forceUpdate()` when you know deep data structures have changed. - */ -var ReactComponentWithPureRenderMixin = { - shouldComponentUpdate: function(nextProps, nextState) { - return !shallowEqual(this.props, nextProps) || - !shallowEqual(this.state, nextState); - } -}; - -module.exports = ReactComponentWithPureRenderMixin; - -},{"./shallowEqual":153}],38:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule ReactCompositeComponent - */ - -"use strict"; - -var ReactComponent = _dereq_("./ReactComponent"); -var ReactContext = _dereq_("./ReactContext"); -var ReactCurrentOwner = _dereq_("./ReactCurrentOwner"); -var ReactDescriptor = _dereq_("./ReactDescriptor"); -var ReactDescriptorValidator = _dereq_("./ReactDescriptorValidator"); -var ReactEmptyComponent = _dereq_("./ReactEmptyComponent"); -var ReactErrorUtils = _dereq_("./ReactErrorUtils"); -var ReactOwner = _dereq_("./ReactOwner"); -var ReactPerf = _dereq_("./ReactPerf"); -var ReactPropTransferer = _dereq_("./ReactPropTransferer"); -var ReactPropTypeLocations = _dereq_("./ReactPropTypeLocations"); -var ReactPropTypeLocationNames = _dereq_("./ReactPropTypeLocationNames"); -var ReactUpdates = _dereq_("./ReactUpdates"); - -var instantiateReactComponent = _dereq_("./instantiateReactComponent"); -var invariant = _dereq_("./invariant"); -var keyMirror = _dereq_("./keyMirror"); -var merge = _dereq_("./merge"); -var mixInto = _dereq_("./mixInto"); -var monitorCodeUse = _dereq_("./monitorCodeUse"); -var mapObject = _dereq_("./mapObject"); -var shouldUpdateReactComponent = _dereq_("./shouldUpdateReactComponent"); -var warning = _dereq_("./warning"); - -/** - * Policies that describe methods in `ReactCompositeComponentInterface`. - */ -var SpecPolicy = keyMirror({ - /** - * These methods may be defined only once by the class specification or mixin. - */ - DEFINE_ONCE: null, - /** - * These methods may be defined by both the class specification and mixins. - * Subsequent definitions will be chained. These methods must return void. - */ - DEFINE_MANY: null, - /** - * These methods are overriding the base ReactCompositeComponent class. - */ - OVERRIDE_BASE: null, - /** - * These methods are similar to DEFINE_MANY, except we assume they return - * objects. We try to merge the keys of the return values of all the mixed in - * functions. If there is a key conflict we throw. - */ - DEFINE_MANY_MERGED: null -}); - - -var injectedMixins = []; - -/** - * Composite components are higher-level components that compose other composite - * or native components. - * - * To create a new type of `ReactCompositeComponent`, pass a specification of - * your new class to `React.createClass`. The only requirement of your class - * specification is that you implement a `render` method. - * - * var MyComponent = React.createClass({ - * render: function() { - * return
Hello World
; - * } - * }); - * - * The class specification supports a specific protocol of methods that have - * special meaning (e.g. `render`). See `ReactCompositeComponentInterface` for - * more the comprehensive protocol. Any other properties and methods in the - * class specification will available on the prototype. - * - * @interface ReactCompositeComponentInterface - * @internal - */ -var ReactCompositeComponentInterface = { - - /** - * An array of Mixin objects to include when defining your component. - * - * @type {array} - * @optional - */ - mixins: SpecPolicy.DEFINE_MANY, - - /** - * An object containing properties and methods that should be defined on - * the component's constructor instead of its prototype (static methods). - * - * @type {object} - * @optional - */ - statics: SpecPolicy.DEFINE_MANY, - - /** - * Definition of prop types for this component. - * - * @type {object} - * @optional - */ - propTypes: SpecPolicy.DEFINE_MANY, - - /** - * Definition of context types for this component. - * - * @type {object} - * @optional - */ - contextTypes: SpecPolicy.DEFINE_MANY, - - /** - * Definition of context types this component sets for its children. - * - * @type {object} - * @optional - */ - childContextTypes: SpecPolicy.DEFINE_MANY, - - // ==== Definition methods ==== - - /** - * Invoked when the component is mounted. Values in the mapping will be set on - * `this.props` if that prop is not specified (i.e. using an `in` check). - * - * This method is invoked before `getInitialState` and therefore cannot rely - * on `this.state` or use `this.setState`. - * - * @return {object} - * @optional - */ - getDefaultProps: SpecPolicy.DEFINE_MANY_MERGED, - - /** - * Invoked once before the component is mounted. The return value will be used - * as the initial value of `this.state`. - * - * getInitialState: function() { - * return { - * isOn: false, - * fooBaz: new BazFoo() - * } - * } - * - * @return {object} - * @optional - */ - getInitialState: SpecPolicy.DEFINE_MANY_MERGED, - - /** - * @return {object} - * @optional - */ - getChildContext: SpecPolicy.DEFINE_MANY_MERGED, - - /** - * Uses props from `this.props` and state from `this.state` to render the - * structure of the component. - * - * No guarantees are made about when or how often this method is invoked, so - * it must not have side effects. - * - * render: function() { - * var name = this.props.name; - * return
Hello, {name}!
; - * } - * - * @return {ReactComponent} - * @nosideeffects - * @required - */ - render: SpecPolicy.DEFINE_ONCE, - - - - // ==== Delegate methods ==== - - /** - * Invoked when the component is initially created and about to be mounted. - * This may have side effects, but any external subscriptions or data created - * by this method must be cleaned up in `componentWillUnmount`. - * - * @optional - */ - componentWillMount: SpecPolicy.DEFINE_MANY, - - /** - * Invoked when the component has been mounted and has a DOM representation. - * However, there is no guarantee that the DOM node is in the document. - * - * Use this as an opportunity to operate on the DOM when the component has - * been mounted (initialized and rendered) for the first time. - * - * @param {DOMElement} rootNode DOM element representing the component. - * @optional - */ - componentDidMount: SpecPolicy.DEFINE_MANY, - - /** - * Invoked before the component receives new props. - * - * Use this as an opportunity to react to a prop transition by updating the - * state using `this.setState`. Current props are accessed via `this.props`. - * - * componentWillReceiveProps: function(nextProps, nextContext) { - * this.setState({ - * likesIncreasing: nextProps.likeCount > this.props.likeCount - * }); - * } - * - * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop - * transition may cause a state change, but the opposite is not true. If you - * need it, you are probably looking for `componentWillUpdate`. - * - * @param {object} nextProps - * @optional - */ - componentWillReceiveProps: SpecPolicy.DEFINE_MANY, - - /** - * Invoked while deciding if the component should be updated as a result of - * receiving new props, state and/or context. - * - * Use this as an opportunity to `return false` when you're certain that the - * transition to the new props/state/context will not require a component - * update. - * - * shouldComponentUpdate: function(nextProps, nextState, nextContext) { - * return !equal(nextProps, this.props) || - * !equal(nextState, this.state) || - * !equal(nextContext, this.context); - * } - * - * @param {object} nextProps - * @param {?object} nextState - * @param {?object} nextContext - * @return {boolean} True if the component should update. - * @optional - */ - shouldComponentUpdate: SpecPolicy.DEFINE_ONCE, - - /** - * Invoked when the component is about to update due to a transition from - * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState` - * and `nextContext`. - * - * Use this as an opportunity to perform preparation before an update occurs. - * - * NOTE: You **cannot** use `this.setState()` in this method. - * - * @param {object} nextProps - * @param {?object} nextState - * @param {?object} nextContext - * @param {ReactReconcileTransaction} transaction - * @optional - */ - componentWillUpdate: SpecPolicy.DEFINE_MANY, - - /** - * Invoked when the component's DOM representation has been updated. - * - * Use this as an opportunity to operate on the DOM when the component has - * been updated. - * - * @param {object} prevProps - * @param {?object} prevState - * @param {?object} prevContext - * @param {DOMElement} rootNode DOM element representing the component. - * @optional - */ - componentDidUpdate: SpecPolicy.DEFINE_MANY, - - /** - * Invoked when the component is about to be removed from its parent and have - * its DOM representation destroyed. - * - * Use this as an opportunity to deallocate any external resources. - * - * NOTE: There is no `componentDidUnmount` since your component will have been - * destroyed by that point. - * - * @optional - */ - componentWillUnmount: SpecPolicy.DEFINE_MANY, - - - - // ==== Advanced methods ==== - - /** - * Updates the component's currently mounted DOM representation. - * - * By default, this implements React's rendering and reconciliation algorithm. - * Sophisticated clients may wish to override this. - * - * @param {ReactReconcileTransaction} transaction - * @internal - * @overridable - */ - updateComponent: SpecPolicy.OVERRIDE_BASE - -}; - -/** - * Mapping from class specification keys to special processing functions. - * - * Although these are declared like instance properties in the specification - * when defining classes using `React.createClass`, they are actually static - * and are accessible on the constructor instead of the prototype. Despite - * being static, they must be defined outside of the "statics" key under - * which all other static methods are defined. - */ -var RESERVED_SPEC_KEYS = { - displayName: function(Constructor, displayName) { - Constructor.displayName = displayName; - }, - mixins: function(Constructor, mixins) { - if (mixins) { - for (var i = 0; i < mixins.length; i++) { - mixSpecIntoComponent(Constructor, mixins[i]); - } - } - }, - childContextTypes: function(Constructor, childContextTypes) { - validateTypeDef( - Constructor, - childContextTypes, - ReactPropTypeLocations.childContext - ); - Constructor.childContextTypes = merge( - Constructor.childContextTypes, - childContextTypes - ); - }, - contextTypes: function(Constructor, contextTypes) { - validateTypeDef( - Constructor, - contextTypes, - ReactPropTypeLocations.context - ); - Constructor.contextTypes = merge(Constructor.contextTypes, contextTypes); - }, - /** - * Special case getDefaultProps which should move into statics but requires - * automatic merging. - */ - getDefaultProps: function(Constructor, getDefaultProps) { - if (Constructor.getDefaultProps) { - Constructor.getDefaultProps = createMergedResultFunction( - Constructor.getDefaultProps, - getDefaultProps - ); - } else { - Constructor.getDefaultProps = getDefaultProps; - } - }, - propTypes: function(Constructor, propTypes) { - validateTypeDef( - Constructor, - propTypes, - ReactPropTypeLocations.prop - ); - Constructor.propTypes = merge(Constructor.propTypes, propTypes); - }, - statics: function(Constructor, statics) { - mixStaticSpecIntoComponent(Constructor, statics); - } -}; - -function getDeclarationErrorAddendum(component) { - var owner = component._owner || null; - if (owner && owner.constructor && owner.constructor.displayName) { - return ' Check the render method of `' + owner.constructor.displayName + - '`.'; - } - return ''; -} - -function validateTypeDef(Constructor, typeDef, location) { - for (var propName in typeDef) { - if (typeDef.hasOwnProperty(propName)) { - ("production" !== "development" ? invariant( - typeof typeDef[propName] == 'function', - '%s: %s type `%s` is invalid; it must be a function, usually from ' + - 'React.PropTypes.', - Constructor.displayName || 'ReactCompositeComponent', - ReactPropTypeLocationNames[location], - propName - ) : invariant(typeof typeDef[propName] == 'function')); - } - } -} - -function validateMethodOverride(proto, name) { - var specPolicy = ReactCompositeComponentInterface.hasOwnProperty(name) ? - ReactCompositeComponentInterface[name] : - null; - - // Disallow overriding of base class methods unless explicitly allowed. - if (ReactCompositeComponentMixin.hasOwnProperty(name)) { - ("production" !== "development" ? invariant( - specPolicy === SpecPolicy.OVERRIDE_BASE, - 'ReactCompositeComponentInterface: You are attempting to override ' + - '`%s` from your class specification. Ensure that your method names ' + - 'do not overlap with React methods.', - name - ) : invariant(specPolicy === SpecPolicy.OVERRIDE_BASE)); - } - - // Disallow defining methods more than once unless explicitly allowed. - if (proto.hasOwnProperty(name)) { - ("production" !== "development" ? invariant( - specPolicy === SpecPolicy.DEFINE_MANY || - specPolicy === SpecPolicy.DEFINE_MANY_MERGED, - 'ReactCompositeComponentInterface: You are attempting to define ' + - '`%s` on your component more than once. This conflict may be due ' + - 'to a mixin.', - name - ) : invariant(specPolicy === SpecPolicy.DEFINE_MANY || - specPolicy === SpecPolicy.DEFINE_MANY_MERGED)); - } -} - -function validateLifeCycleOnReplaceState(instance) { - var compositeLifeCycleState = instance._compositeLifeCycleState; - ("production" !== "development" ? invariant( - instance.isMounted() || - compositeLifeCycleState === CompositeLifeCycle.MOUNTING, - 'replaceState(...): Can only update a mounted or mounting component.' - ) : invariant(instance.isMounted() || - compositeLifeCycleState === CompositeLifeCycle.MOUNTING)); - ("production" !== "development" ? invariant(compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE, - 'replaceState(...): Cannot update during an existing state transition ' + - '(such as within `render`). This could potentially cause an infinite ' + - 'loop so it is forbidden.' - ) : invariant(compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE)); - ("production" !== "development" ? invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING, - 'replaceState(...): Cannot update while unmounting component. This ' + - 'usually means you called setState() on an unmounted component.' - ) : invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING)); -} - -/** - * Custom version of `mixInto` which handles policy validation and reserved - * specification keys when building `ReactCompositeComponent` classses. - */ -function mixSpecIntoComponent(Constructor, spec) { - ("production" !== "development" ? invariant( - !ReactDescriptor.isValidFactory(spec), - 'ReactCompositeComponent: You\'re attempting to ' + - 'use a component class as a mixin. Instead, just use a regular object.' - ) : invariant(!ReactDescriptor.isValidFactory(spec))); - ("production" !== "development" ? invariant( - !ReactDescriptor.isValidDescriptor(spec), - 'ReactCompositeComponent: You\'re attempting to ' + - 'use a component as a mixin. Instead, just use a regular object.' - ) : invariant(!ReactDescriptor.isValidDescriptor(spec))); - - var proto = Constructor.prototype; - for (var name in spec) { - var property = spec[name]; - if (!spec.hasOwnProperty(name)) { - continue; - } - - validateMethodOverride(proto, name); - - if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { - RESERVED_SPEC_KEYS[name](Constructor, property); - } else { - // Setup methods on prototype: - // The following member methods should not be automatically bound: - // 1. Expected ReactCompositeComponent methods (in the "interface"). - // 2. Overridden methods (that were mixed in). - var isCompositeComponentMethod = - ReactCompositeComponentInterface.hasOwnProperty(name); - var isAlreadyDefined = proto.hasOwnProperty(name); - var markedDontBind = property && property.__reactDontBind; - var isFunction = typeof property === 'function'; - var shouldAutoBind = - isFunction && - !isCompositeComponentMethod && - !isAlreadyDefined && - !markedDontBind; - - if (shouldAutoBind) { - if (!proto.__reactAutoBindMap) { - proto.__reactAutoBindMap = {}; - } - proto.__reactAutoBindMap[name] = property; - proto[name] = property; - } else { - if (isAlreadyDefined) { - var specPolicy = ReactCompositeComponentInterface[name]; - - // These cases should already be caught by validateMethodOverride - ("production" !== "development" ? invariant( - isCompositeComponentMethod && ( - specPolicy === SpecPolicy.DEFINE_MANY_MERGED || - specPolicy === SpecPolicy.DEFINE_MANY - ), - 'ReactCompositeComponent: Unexpected spec policy %s for key %s ' + - 'when mixing in component specs.', - specPolicy, - name - ) : invariant(isCompositeComponentMethod && ( - specPolicy === SpecPolicy.DEFINE_MANY_MERGED || - specPolicy === SpecPolicy.DEFINE_MANY - ))); - - // For methods which are defined more than once, call the existing - // methods before calling the new property, merging if appropriate. - if (specPolicy === SpecPolicy.DEFINE_MANY_MERGED) { - proto[name] = createMergedResultFunction(proto[name], property); - } else if (specPolicy === SpecPolicy.DEFINE_MANY) { - proto[name] = createChainedFunction(proto[name], property); - } - } else { - proto[name] = property; - if ("production" !== "development") { - // Add verbose displayName to the function, which helps when looking - // at profiling tools. - if (typeof property === 'function' && spec.displayName) { - proto[name].displayName = spec.displayName + '_' + name; - } - } - } - } - } - } -} - -function mixStaticSpecIntoComponent(Constructor, statics) { - if (!statics) { - return; - } - for (var name in statics) { - var property = statics[name]; - if (!statics.hasOwnProperty(name)) { - continue; - } - - var isInherited = name in Constructor; - var result = property; - if (isInherited) { - var existingProperty = Constructor[name]; - var existingType = typeof existingProperty; - var propertyType = typeof property; - ("production" !== "development" ? invariant( - existingType === 'function' && propertyType === 'function', - 'ReactCompositeComponent: You are attempting to define ' + - '`%s` on your component more than once, but that is only supported ' + - 'for functions, which are chained together. This conflict may be ' + - 'due to a mixin.', - name - ) : invariant(existingType === 'function' && propertyType === 'function')); - result = createChainedFunction(existingProperty, property); - } - Constructor[name] = result; - } -} - -/** - * Merge two objects, but throw if both contain the same key. - * - * @param {object} one The first object, which is mutated. - * @param {object} two The second object - * @return {object} one after it has been mutated to contain everything in two. - */ -function mergeObjectsWithNoDuplicateKeys(one, two) { - ("production" !== "development" ? invariant( - one && two && typeof one === 'object' && typeof two === 'object', - 'mergeObjectsWithNoDuplicateKeys(): Cannot merge non-objects' - ) : invariant(one && two && typeof one === 'object' && typeof two === 'object')); - - mapObject(two, function(value, key) { - ("production" !== "development" ? invariant( - one[key] === undefined, - 'mergeObjectsWithNoDuplicateKeys(): ' + - 'Tried to merge two objects with the same key: %s', - key - ) : invariant(one[key] === undefined)); - one[key] = value; - }); - return one; -} - -/** - * Creates a function that invokes two functions and merges their return values. - * - * @param {function} one Function to invoke first. - * @param {function} two Function to invoke second. - * @return {function} Function that invokes the two argument functions. - * @private - */ -function createMergedResultFunction(one, two) { - return function mergedResult() { - var a = one.apply(this, arguments); - var b = two.apply(this, arguments); - if (a == null) { - return b; - } else if (b == null) { - return a; - } - return mergeObjectsWithNoDuplicateKeys(a, b); - }; -} - -/** - * Creates a function that invokes two functions and ignores their return vales. - * - * @param {function} one Function to invoke first. - * @param {function} two Function to invoke second. - * @return {function} Function that invokes the two argument functions. - * @private - */ -function createChainedFunction(one, two) { - return function chainedFunction() { - one.apply(this, arguments); - two.apply(this, arguments); - }; -} - -/** - * `ReactCompositeComponent` maintains an auxiliary life cycle state in - * `this._compositeLifeCycleState` (which can be null). - * - * This is different from the life cycle state maintained by `ReactComponent` in - * `this._lifeCycleState`. The following diagram shows how the states overlap in - * time. There are times when the CompositeLifeCycle is null - at those times it - * is only meaningful to look at ComponentLifeCycle alone. - * - * Top Row: ReactComponent.ComponentLifeCycle - * Low Row: ReactComponent.CompositeLifeCycle - * - * +-------+------------------------------------------------------+--------+ - * | UN | MOUNTED | UN | - * |MOUNTED| | MOUNTED| - * +-------+------------------------------------------------------+--------+ - * | ^--------+ +------+ +------+ +------+ +--------^ | - * | | | | | | | | | | | | - * | 0--|MOUNTING|-0-|RECEIV|-0-|RECEIV|-0-|RECEIV|-0-| UN |--->0 | - * | | | |PROPS | | PROPS| | STATE| |MOUNTING| | - * | | | | | | | | | | | | - * | | | | | | | | | | | | - * | +--------+ +------+ +------+ +------+ +--------+ | - * | | | | - * +-------+------------------------------------------------------+--------+ - */ -var CompositeLifeCycle = keyMirror({ - /** - * Components in the process of being mounted respond to state changes - * differently. - */ - MOUNTING: null, - /** - * Components in the process of being unmounted are guarded against state - * changes. - */ - UNMOUNTING: null, - /** - * Components that are mounted and receiving new props respond to state - * changes differently. - */ - RECEIVING_PROPS: null, - /** - * Components that are mounted and receiving new state are guarded against - * additional state changes. - */ - RECEIVING_STATE: null -}); - -/** - * @lends {ReactCompositeComponent.prototype} - */ -var ReactCompositeComponentMixin = { - - /** - * Base constructor for all composite component. - * - * @param {ReactDescriptor} descriptor - * @final - * @internal - */ - construct: function(descriptor) { - // Children can be either an array or more than one argument - ReactComponent.Mixin.construct.apply(this, arguments); - ReactOwner.Mixin.construct.apply(this, arguments); - - this.state = null; - this._pendingState = null; - - // This is the public post-processed context. The real context and pending - // context lives on the descriptor. - this.context = null; - - this._compositeLifeCycleState = null; - }, - - /** - * Checks whether or not this composite component is mounted. - * @return {boolean} True if mounted, false otherwise. - * @protected - * @final - */ - isMounted: function() { - return ReactComponent.Mixin.isMounted.call(this) && - this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING; - }, - - /** - * Initializes the component, renders markup, and registers event listeners. - * - * @param {string} rootID DOM ID of the root node. - * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction - * @param {number} mountDepth number of components in the owner hierarchy - * @return {?string} Rendered markup to be inserted into the DOM. - * @final - * @internal - */ - mountComponent: ReactPerf.measure( - 'ReactCompositeComponent', - 'mountComponent', - function(rootID, transaction, mountDepth) { - ReactComponent.Mixin.mountComponent.call( - this, - rootID, - transaction, - mountDepth - ); - this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING; - - if (this.__reactAutoBindMap) { - this._bindAutoBindMethods(); - } - - this.context = this._processContext(this._descriptor._context); - this.props = this._processProps(this.props); - - this.state = this.getInitialState ? this.getInitialState() : null; - ("production" !== "development" ? invariant( - typeof this.state === 'object' && !Array.isArray(this.state), - '%s.getInitialState(): must return an object or null', - this.constructor.displayName || 'ReactCompositeComponent' - ) : invariant(typeof this.state === 'object' && !Array.isArray(this.state))); - - this._pendingState = null; - this._pendingForceUpdate = false; - - if (this.componentWillMount) { - this.componentWillMount(); - // When mounting, calls to `setState` by `componentWillMount` will set - // `this._pendingState` without triggering a re-render. - if (this._pendingState) { - this.state = this._pendingState; - this._pendingState = null; - } - } - - this._renderedComponent = instantiateReactComponent( - this._renderValidatedComponent() - ); - - // Done with mounting, `setState` will now trigger UI changes. - this._compositeLifeCycleState = null; - var markup = this._renderedComponent.mountComponent( - rootID, - transaction, - mountDepth + 1 - ); - if (this.componentDidMount) { - transaction.getReactMountReady().enqueue(this.componentDidMount, this); - } - return markup; - } - ), - - /** - * Releases any resources allocated by `mountComponent`. - * - * @final - * @internal - */ - unmountComponent: function() { - this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING; - if (this.componentWillUnmount) { - this.componentWillUnmount(); - } - this._compositeLifeCycleState = null; - - this._renderedComponent.unmountComponent(); - this._renderedComponent = null; - - ReactComponent.Mixin.unmountComponent.call(this); - - // Some existing components rely on this.props even after they've been - // destroyed (in event handlers). - // TODO: this.props = null; - // TODO: this.state = null; - }, - - /** - * Sets a subset of the state. Always use this or `replaceState` to mutate - * state. You should treat `this.state` as immutable. - * - * There is no guarantee that `this.state` will be immediately updated, so - * accessing `this.state` after calling this method may return the old value. - * - * There is no guarantee that calls to `setState` will run synchronously, - * as they may eventually be batched together. You can provide an optional - * callback that will be executed when the call to setState is actually - * completed. - * - * @param {object} partialState Next partial state to be merged with state. - * @param {?function} callback Called after state is updated. - * @final - * @protected - */ - setState: function(partialState, callback) { - ("production" !== "development" ? invariant( - typeof partialState === 'object' || partialState == null, - 'setState(...): takes an object of state variables to update.' - ) : invariant(typeof partialState === 'object' || partialState == null)); - if ("production" !== "development"){ - ("production" !== "development" ? warning( - partialState != null, - 'setState(...): You passed an undefined or null state object; ' + - 'instead, use forceUpdate().' - ) : null); - } - // Merge with `_pendingState` if it exists, otherwise with existing state. - this.replaceState( - merge(this._pendingState || this.state, partialState), - callback - ); - }, - - /** - * Replaces all of the state. Always use this or `setState` to mutate state. - * You should treat `this.state` as immutable. - * - * There is no guarantee that `this.state` will be immediately updated, so - * accessing `this.state` after calling this method may return the old value. - * - * @param {object} completeState Next state. - * @param {?function} callback Called after state is updated. - * @final - * @protected - */ - replaceState: function(completeState, callback) { - validateLifeCycleOnReplaceState(this); - this._pendingState = completeState; - if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) { - // If we're in a componentWillMount handler, don't enqueue a rerender - // because ReactUpdates assumes we're in a browser context (which is wrong - // for server rendering) and we're about to do a render anyway. - // TODO: The callback here is ignored when setState is called from - // componentWillMount. Either fix it or disallow doing so completely in - // favor of getInitialState. - ReactUpdates.enqueueUpdate(this, callback); - } - }, - - /** - * Filters the context object to only contain keys specified in - * `contextTypes`, and asserts that they are valid. - * - * @param {object} context - * @return {?object} - * @private - */ - _processContext: function(context) { - var maskedContext = null; - var contextTypes = this.constructor.contextTypes; - if (contextTypes) { - maskedContext = {}; - for (var contextName in contextTypes) { - maskedContext[contextName] = context[contextName]; - } - if ("production" !== "development") { - this._checkPropTypes( - contextTypes, - maskedContext, - ReactPropTypeLocations.context - ); - } - } - return maskedContext; - }, - - /** - * @param {object} currentContext - * @return {object} - * @private - */ - _processChildContext: function(currentContext) { - var childContext = this.getChildContext && this.getChildContext(); - var displayName = this.constructor.displayName || 'ReactCompositeComponent'; - if (childContext) { - ("production" !== "development" ? invariant( - typeof this.constructor.childContextTypes === 'object', - '%s.getChildContext(): childContextTypes must be defined in order to ' + - 'use getChildContext().', - displayName - ) : invariant(typeof this.constructor.childContextTypes === 'object')); - if ("production" !== "development") { - this._checkPropTypes( - this.constructor.childContextTypes, - childContext, - ReactPropTypeLocations.childContext - ); - } - for (var name in childContext) { - ("production" !== "development" ? invariant( - name in this.constructor.childContextTypes, - '%s.getChildContext(): key "%s" is not defined in childContextTypes.', - displayName, - name - ) : invariant(name in this.constructor.childContextTypes)); - } - return merge(currentContext, childContext); - } - return currentContext; - }, - - /** - * Processes props by setting default values for unspecified props and - * asserting that the props are valid. Does not mutate its argument; returns - * a new props object with defaults merged in. - * - * @param {object} newProps - * @return {object} - * @private - */ - _processProps: function(newProps) { - var defaultProps = this.constructor.defaultProps; - var props; - if (defaultProps) { - props = merge(newProps); - for (var propName in defaultProps) { - if (typeof props[propName] === 'undefined') { - props[propName] = defaultProps[propName]; - } - } - } else { - props = newProps; - } - if ("production" !== "development") { - var propTypes = this.constructor.propTypes; - if (propTypes) { - this._checkPropTypes(propTypes, props, ReactPropTypeLocations.prop); - } - } - return props; - }, - - /** - * Assert that the props are valid - * - * @param {object} propTypes Map of prop name to a ReactPropType - * @param {object} props - * @param {string} location e.g. "prop", "context", "child context" - * @private - */ - _checkPropTypes: function(propTypes, props, location) { - // TODO: Stop validating prop types here and only use the descriptor - // validation. - var componentName = this.constructor.displayName; - for (var propName in propTypes) { - if (propTypes.hasOwnProperty(propName)) { - var error = - propTypes[propName](props, propName, componentName, location); - if (error instanceof Error) { - // We may want to extend this logic for similar errors in - // renderComponent calls, so I'm abstracting it away into - // a function to minimize refactoring in the future - var addendum = getDeclarationErrorAddendum(this); - ("production" !== "development" ? warning(false, error.message + addendum) : null); - } - } - } - }, - - /** - * If any of `_pendingDescriptor`, `_pendingState`, or `_pendingForceUpdate` - * is set, update the component. - * - * @param {ReactReconcileTransaction} transaction - * @internal - */ - performUpdateIfNecessary: function(transaction) { - var compositeLifeCycleState = this._compositeLifeCycleState; - // Do not trigger a state transition if we are in the middle of mounting or - // receiving props because both of those will already be doing this. - if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || - compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) { - return; - } - - if (this._pendingDescriptor == null && - this._pendingState == null && - !this._pendingForceUpdate) { - return; - } - - var nextContext = this.context; - var nextProps = this.props; - var nextDescriptor = this._descriptor; - if (this._pendingDescriptor != null) { - nextDescriptor = this._pendingDescriptor; - nextContext = this._processContext(nextDescriptor._context); - nextProps = this._processProps(nextDescriptor.props); - this._pendingDescriptor = null; - - this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS; - if (this.componentWillReceiveProps) { - this.componentWillReceiveProps(nextProps, nextContext); - } - } - - this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE; - - var nextState = this._pendingState || this.state; - this._pendingState = null; - - try { - var shouldUpdate = - this._pendingForceUpdate || - !this.shouldComponentUpdate || - this.shouldComponentUpdate(nextProps, nextState, nextContext); - - if ("production" !== "development") { - if (typeof shouldUpdate === "undefined") { - console.warn( - (this.constructor.displayName || 'ReactCompositeComponent') + - '.shouldComponentUpdate(): Returned undefined instead of a ' + - 'boolean value. Make sure to return true or false.' - ); - } - } - - if (shouldUpdate) { - this._pendingForceUpdate = false; - // Will set `this.props`, `this.state` and `this.context`. - this._performComponentUpdate( - nextDescriptor, - nextProps, - nextState, - nextContext, - transaction - ); - } else { - // If it's determined that a component should not update, we still want - // to set props and state. - this._descriptor = nextDescriptor; - this.props = nextProps; - this.state = nextState; - this.context = nextContext; - - // Owner cannot change because shouldUpdateReactComponent doesn't allow - // it. TODO: Remove this._owner completely. - this._owner = nextDescriptor._owner; - } - } finally { - this._compositeLifeCycleState = null; - } - }, - - /** - * Merges new props and state, notifies delegate methods of update and - * performs update. - * - * @param {ReactDescriptor} nextDescriptor Next descriptor - * @param {object} nextProps Next public object to set as properties. - * @param {?object} nextState Next object to set as state. - * @param {?object} nextContext Next public object to set as context. - * @param {ReactReconcileTransaction} transaction - * @private - */ - _performComponentUpdate: function( - nextDescriptor, - nextProps, - nextState, - nextContext, - transaction - ) { - var prevDescriptor = this._descriptor; - var prevProps = this.props; - var prevState = this.state; - var prevContext = this.context; - - if (this.componentWillUpdate) { - this.componentWillUpdate(nextProps, nextState, nextContext); - } - - this._descriptor = nextDescriptor; - this.props = nextProps; - this.state = nextState; - this.context = nextContext; - - // Owner cannot change because shouldUpdateReactComponent doesn't allow - // it. TODO: Remove this._owner completely. - this._owner = nextDescriptor._owner; - - this.updateComponent( - transaction, - prevDescriptor - ); - - if (this.componentDidUpdate) { - transaction.getReactMountReady().enqueue( - this.componentDidUpdate.bind(this, prevProps, prevState, prevContext), - this - ); - } - }, - - receiveComponent: function(nextDescriptor, transaction) { - if (nextDescriptor === this._descriptor && - nextDescriptor._owner != null) { - // Since descriptors are immutable after the owner is rendered, - // we can do a cheap identity compare here to determine if this is a - // superfluous reconcile. It's possible for state to be mutable but such - // change should trigger an update of the owner which would recreate - // the descriptor. We explicitly check for the existence of an owner since - // it's possible for a descriptor created outside a composite to be - // deeply mutated and reused. - return; - } - - ReactComponent.Mixin.receiveComponent.call( - this, - nextDescriptor, - transaction - ); - }, - - /** - * Updates the component's currently mounted DOM representation. - * - * By default, this implements React's rendering and reconciliation algorithm. - * Sophisticated clients may wish to override this. - * - * @param {ReactReconcileTransaction} transaction - * @param {ReactDescriptor} prevDescriptor - * @internal - * @overridable - */ - updateComponent: ReactPerf.measure( - 'ReactCompositeComponent', - 'updateComponent', - function(transaction, prevParentDescriptor) { - ReactComponent.Mixin.updateComponent.call( - this, - transaction, - prevParentDescriptor - ); - - var prevComponentInstance = this._renderedComponent; - var prevDescriptor = prevComponentInstance._descriptor; - var nextDescriptor = this._renderValidatedComponent(); - if (shouldUpdateReactComponent(prevDescriptor, nextDescriptor)) { - prevComponentInstance.receiveComponent(nextDescriptor, transaction); - } else { - // These two IDs are actually the same! But nothing should rely on that. - var thisID = this._rootNodeID; - var prevComponentID = prevComponentInstance._rootNodeID; - prevComponentInstance.unmountComponent(); - this._renderedComponent = instantiateReactComponent(nextDescriptor); - var nextMarkup = this._renderedComponent.mountComponent( - thisID, - transaction, - this._mountDepth + 1 - ); - ReactComponent.BackendIDOperations.dangerouslyReplaceNodeWithMarkupByID( - prevComponentID, - nextMarkup - ); - } - } - ), - - /** - * Forces an update. This should only be invoked when it is known with - * certainty that we are **not** in a DOM transaction. - * - * You may want to call this when you know that some deeper aspect of the - * component's state has changed but `setState` was not called. - * - * This will not invoke `shouldUpdateComponent`, but it will invoke - * `componentWillUpdate` and `componentDidUpdate`. - * - * @param {?function} callback Called after update is complete. - * @final - * @protected - */ - forceUpdate: function(callback) { - var compositeLifeCycleState = this._compositeLifeCycleState; - ("production" !== "development" ? invariant( - this.isMounted() || - compositeLifeCycleState === CompositeLifeCycle.MOUNTING, - 'forceUpdate(...): Can only force an update on mounted or mounting ' + - 'components.' - ) : invariant(this.isMounted() || - compositeLifeCycleState === CompositeLifeCycle.MOUNTING)); - ("production" !== "development" ? invariant( - compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE && - compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING, - 'forceUpdate(...): Cannot force an update while unmounting component ' + - 'or during an existing state transition (such as within `render`).' - ) : invariant(compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE && - compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING)); - this._pendingForceUpdate = true; - ReactUpdates.enqueueUpdate(this, callback); - }, - - /** - * @private - */ - _renderValidatedComponent: ReactPerf.measure( - 'ReactCompositeComponent', - '_renderValidatedComponent', - function() { - var renderedComponent; - var previousContext = ReactContext.current; - ReactContext.current = this._processChildContext( - this._descriptor._context - ); - ReactCurrentOwner.current = this; - try { - renderedComponent = this.render(); - if (renderedComponent === null || renderedComponent === false) { - renderedComponent = ReactEmptyComponent.getEmptyComponent(); - ReactEmptyComponent.registerNullComponentID(this._rootNodeID); - } else { - ReactEmptyComponent.deregisterNullComponentID(this._rootNodeID); - } - } finally { - ReactContext.current = previousContext; - ReactCurrentOwner.current = null; - } - ("production" !== "development" ? invariant( - ReactDescriptor.isValidDescriptor(renderedComponent), - '%s.render(): A valid ReactComponent must be returned. You may have ' + - 'returned undefined, an array or some other invalid object.', - this.constructor.displayName || 'ReactCompositeComponent' - ) : invariant(ReactDescriptor.isValidDescriptor(renderedComponent))); - return renderedComponent; - } - ), - - /** - * @private - */ - _bindAutoBindMethods: function() { - for (var autoBindKey in this.__reactAutoBindMap) { - if (!this.__reactAutoBindMap.hasOwnProperty(autoBindKey)) { - continue; - } - var method = this.__reactAutoBindMap[autoBindKey]; - this[autoBindKey] = this._bindAutoBindMethod(ReactErrorUtils.guard( - method, - this.constructor.displayName + '.' + autoBindKey - )); - } - }, - - /** - * Binds a method to the component. - * - * @param {function} method Method to be bound. - * @private - */ - _bindAutoBindMethod: function(method) { - var component = this; - var boundMethod = function() { - return method.apply(component, arguments); - }; - if ("production" !== "development") { - boundMethod.__reactBoundContext = component; - boundMethod.__reactBoundMethod = method; - boundMethod.__reactBoundArguments = null; - var componentName = component.constructor.displayName; - var _bind = boundMethod.bind; - boundMethod.bind = function(newThis ) {var args=Array.prototype.slice.call(arguments,1); - // User is trying to bind() an autobound method; we effectively will - // ignore the value of "this" that the user is trying to use, so - // let's warn. - if (newThis !== component && newThis !== null) { - monitorCodeUse('react_bind_warning', { component: componentName }); - console.warn( - 'bind(): React component methods may only be bound to the ' + - 'component instance. See ' + componentName - ); - } else if (!args.length) { - monitorCodeUse('react_bind_warning', { component: componentName }); - console.warn( - 'bind(): You are binding a component method to the component. ' + - 'React does this for you automatically in a high-performance ' + - 'way, so you can safely remove this call. See ' + componentName - ); - return boundMethod; - } - var reboundMethod = _bind.apply(boundMethod, arguments); - reboundMethod.__reactBoundContext = component; - reboundMethod.__reactBoundMethod = method; - reboundMethod.__reactBoundArguments = args; - return reboundMethod; - }; - } - return boundMethod; - } -}; - -var ReactCompositeComponentBase = function() {}; -mixInto(ReactCompositeComponentBase, ReactComponent.Mixin); -mixInto(ReactCompositeComponentBase, ReactOwner.Mixin); -mixInto(ReactCompositeComponentBase, ReactPropTransferer.Mixin); -mixInto(ReactCompositeComponentBase, ReactCompositeComponentMixin); - -/** - * Module for creating composite components. - * - * @class ReactCompositeComponent - * @extends ReactComponent - * @extends ReactOwner - * @extends ReactPropTransferer - */ -var ReactCompositeComponent = { - - LifeCycle: CompositeLifeCycle, - - Base: ReactCompositeComponentBase, - - /** - * Creates a composite component class given a class specification. - * - * @param {object} spec Class specification (which must define `render`). - * @return {function} Component constructor function. - * @public - */ - createClass: function(spec) { - var Constructor = function(props, owner) { - this.construct(props, owner); - }; - Constructor.prototype = new ReactCompositeComponentBase(); - Constructor.prototype.constructor = Constructor; - - injectedMixins.forEach( - mixSpecIntoComponent.bind(null, Constructor) - ); - - mixSpecIntoComponent(Constructor, spec); - - // Initialize the defaultProps property after all mixins have been merged - if (Constructor.getDefaultProps) { - Constructor.defaultProps = Constructor.getDefaultProps(); - } - - ("production" !== "development" ? invariant( - Constructor.prototype.render, - 'createClass(...): Class specification must implement a `render` method.' - ) : invariant(Constructor.prototype.render)); - - if ("production" !== "development") { - if (Constructor.prototype.componentShouldUpdate) { - monitorCodeUse( - 'react_component_should_update_warning', - { component: spec.displayName } - ); - console.warn( - (spec.displayName || 'A component') + ' has a method called ' + - 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + - 'The name is phrased as a question because the function is ' + - 'expected to return a value.' - ); - } - } - - // Reduce time spent doing lookups by setting these on the prototype. - for (var methodName in ReactCompositeComponentInterface) { - if (!Constructor.prototype[methodName]) { - Constructor.prototype[methodName] = null; - } - } - - var descriptorFactory = ReactDescriptor.createFactory(Constructor); - - if ("production" !== "development") { - return ReactDescriptorValidator.createFactory( - descriptorFactory, - Constructor.propTypes, - Constructor.contextTypes - ); - } - - return descriptorFactory; - }, - - injection: { - injectMixin: function(mixin) { - injectedMixins.push(mixin); - } - } -}; - -module.exports = ReactCompositeComponent; - -},{"./ReactComponent":35,"./ReactContext":39,"./ReactCurrentOwner":40,"./ReactDescriptor":56,"./ReactDescriptorValidator":57,"./ReactEmptyComponent":58,"./ReactErrorUtils":59,"./ReactOwner":70,"./ReactPerf":71,"./ReactPropTransferer":72,"./ReactPropTypeLocationNames":73,"./ReactPropTypeLocations":74,"./ReactUpdates":87,"./instantiateReactComponent":133,"./invariant":134,"./keyMirror":140,"./mapObject":142,"./merge":144,"./mixInto":147,"./monitorCodeUse":148,"./shouldUpdateReactComponent":154,"./warning":158}],39:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule ReactContext - */ - -"use strict"; - -var merge = _dereq_("./merge"); - -/** - * Keeps track of the current context. - * - * The context is automatically passed down the component ownership hierarchy - * and is accessible via `this.context` on ReactCompositeComponents. - */ -var ReactContext = { - - /** - * @internal - * @type {object} - */ - current: {}, - - /** - * Temporarily extends the current context while executing scopedCallback. - * - * A typical use case might look like - * - * render: function() { - * var children = ReactContext.withContext({foo: 'foo'} () => ( - * - * )); - * return
{children}
; - * } - * - * @param {object} newContext New context to merge into the existing context - * @param {function} scopedCallback Callback to run with the new context - * @return {ReactComponent|array} - */ - withContext: function(newContext, scopedCallback) { - var result; - var previousContext = ReactContext.current; - ReactContext.current = merge(previousContext, newContext); - try { - result = scopedCallback(); - } finally { - ReactContext.current = previousContext; - } - return result; - } - -}; - -module.exports = ReactContext; - -},{"./merge":144}],40:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule ReactCurrentOwner - */ - -"use strict"; - -/** - * Keeps track of the current owner. - * - * The current owner is the component who should own any components that are - * currently being constructed. - * - * The depth indicate how many composite components are above this render level. - */ -var ReactCurrentOwner = { - - /** - * @internal - * @type {ReactComponent} - */ - current: null - -}; - -module.exports = ReactCurrentOwner; - -},{}],41:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule ReactDOM - * @typechecks static-only - */ - -"use strict"; - -var ReactDescriptor = _dereq_("./ReactDescriptor"); -var ReactDescriptorValidator = _dereq_("./ReactDescriptorValidator"); -var ReactDOMComponent = _dereq_("./ReactDOMComponent"); - -var mergeInto = _dereq_("./mergeInto"); -var mapObject = _dereq_("./mapObject"); - -/** - * Creates a new React class that is idempotent and capable of containing other - * React components. It accepts event listeners and DOM properties that are - * valid according to `DOMProperty`. - * - * - Event listeners: `onClick`, `onMouseDown`, etc. - * - DOM properties: `className`, `name`, `title`, etc. - * - * The `style` property functions differently from the DOM API. It accepts an - * object mapping of style properties to values. - * - * @param {boolean} omitClose True if the close tag should be omitted. - * @param {string} tag Tag name (e.g. `div`). - * @private - */ -function createDOMComponentClass(omitClose, tag) { - var Constructor = function(descriptor) { - this.construct(descriptor); - }; - Constructor.prototype = new ReactDOMComponent(tag, omitClose); - Constructor.prototype.constructor = Constructor; - Constructor.displayName = tag; - - var ConvenienceConstructor = ReactDescriptor.createFactory(Constructor); - - if ("production" !== "development") { - return ReactDescriptorValidator.createFactory( - ConvenienceConstructor - ); - } - - return ConvenienceConstructor; -} - -/** - * Creates a mapping from supported HTML tags to `ReactDOMComponent` classes. - * This is also accessible via `React.DOM`. - * - * @public - */ -var ReactDOM = mapObject({ - a: false, - abbr: false, - address: false, - area: true, - article: false, - aside: false, - audio: false, - b: false, - base: true, - bdi: false, - bdo: false, - big: false, - blockquote: false, - body: false, - br: true, - button: false, - canvas: false, - caption: false, - cite: false, - code: false, - col: true, - colgroup: false, - data: false, - datalist: false, - dd: false, - del: false, - details: false, - dfn: false, - dialog: false, - div: false, - dl: false, - dt: false, - em: false, - embed: true, - fieldset: false, - figcaption: false, - figure: false, - footer: false, - form: false, // NOTE: Injected, see `ReactDOMForm`. - h1: false, - h2: false, - h3: false, - h4: false, - h5: false, - h6: false, - head: false, - header: false, - hr: true, - html: false, - i: false, - iframe: false, - img: true, - input: true, - ins: false, - kbd: false, - keygen: true, - label: false, - legend: false, - li: false, - link: true, - main: false, - map: false, - mark: false, - menu: false, - menuitem: false, // NOTE: Close tag should be omitted, but causes problems. - meta: true, - meter: false, - nav: false, - noscript: false, - object: false, - ol: false, - optgroup: false, - option: false, - output: false, - p: false, - param: true, - picture: false, - pre: false, - progress: false, - q: false, - rp: false, - rt: false, - ruby: false, - s: false, - samp: false, - script: false, - section: false, - select: false, - small: false, - source: true, - span: false, - strong: false, - style: false, - sub: false, - summary: false, - sup: false, - table: false, - tbody: false, - td: false, - textarea: false, // NOTE: Injected, see `ReactDOMTextarea`. - tfoot: false, - th: false, - thead: false, - time: false, - title: false, - tr: false, - track: true, - u: false, - ul: false, - 'var': false, - video: false, - wbr: true, - - // SVG - circle: false, - defs: false, - ellipse: false, - g: false, - line: false, - linearGradient: false, - mask: false, - path: false, - pattern: false, - polygon: false, - polyline: false, - radialGradient: false, - rect: false, - stop: false, - svg: false, - text: false, - tspan: false -}, createDOMComponentClass); - -var injection = { - injectComponentClasses: function(componentClasses) { - mergeInto(ReactDOM, componentClasses); - } -}; - -ReactDOM.injection = injection; - -module.exports = ReactDOM; - -},{"./ReactDOMComponent":43,"./ReactDescriptor":56,"./ReactDescriptorValidator":57,"./mapObject":142,"./mergeInto":146}],42:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule ReactDOMButton - */ - -"use strict"; - -var AutoFocusMixin = _dereq_("./AutoFocusMixin"); -var ReactBrowserComponentMixin = _dereq_("./ReactBrowserComponentMixin"); -var ReactCompositeComponent = _dereq_("./ReactCompositeComponent"); -var ReactDOM = _dereq_("./ReactDOM"); - -var keyMirror = _dereq_("./keyMirror"); - -// Store a reference to the