Merge lp:~bhdouglass/rockwork/gps-fix into lp:rockwork
- gps-fix
- Merge into trunk
Proposed by
Brian Douglass
Status: | Superseded |
---|---|
Proposed branch: | lp:~bhdouglass/rockwork/gps-fix |
Merge into: | lp:rockwork |
Diff against target: |
5721 lines (+3075/-2444) 29 files modified
rockworkd/libpebble/jsfiles.qrc (+0/-5) rockworkd/libpebble/jskit/cacheLocalStorage.js (+11/-0) rockworkd/libpebble/jskit/jsfiles.qrc (+7/-0) rockworkd/libpebble/jskit/jskitconsole.cpp (+29/-0) rockworkd/libpebble/jskit/jskitconsole.h (+20/-0) rockworkd/libpebble/jskit/jskitgeolocation.cpp (+302/-0) rockworkd/libpebble/jskit/jskitgeolocation.h (+66/-0) rockworkd/libpebble/jskit/jskitlocalstorage.cpp (+117/-0) rockworkd/libpebble/jskit/jskitlocalstorage.h (+40/-0) rockworkd/libpebble/jskit/jskitmanager.cpp (+240/-0) rockworkd/libpebble/jskit/jskitmanager.h (+72/-0) rockworkd/libpebble/jskit/jskitpebble.cpp (+355/-0) rockworkd/libpebble/jskit/jskitpebble.h (+47/-0) rockworkd/libpebble/jskit/jskitperformance.cpp (+13/-0) rockworkd/libpebble/jskit/jskitperformance.h (+20/-0) rockworkd/libpebble/jskit/jskitsetup.js (+191/-0) rockworkd/libpebble/jskit/jskittimer.cpp (+77/-0) rockworkd/libpebble/jskit/jskittimer.h (+31/-0) rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp (+288/-0) rockworkd/libpebble/jskit/jskitxmlhttprequest.h (+91/-0) rockworkd/libpebble/jskit/typedarray.js (+1030/-0) rockworkd/libpebble/jskitmanager.cpp (+0/-243) rockworkd/libpebble/jskitmanager.h (+0/-65) rockworkd/libpebble/jskitobjects.cpp (+0/-859) rockworkd/libpebble/jskitobjects.h (+0/-235) rockworkd/libpebble/pebble.cpp (+8/-2) rockworkd/libpebble/pebble.h (+3/-0) rockworkd/libpebble/typedarray.js (+0/-1030) rockworkd/rockworkd.pro (+17/-5) |
To merge this branch: | bzr merge lp:~bhdouglass/rockwork/gps-fix |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Zanetti | Pending | ||
Review via email: mp+285263@code.launchpad.net |
This proposal has been superseded by a proposal from 2016-02-06.
Commit message
Description of the change
Also includes the jskit changes.
Removes the gps source when not needed.
To post a comment you must log in.
lp:~bhdouglass/rockwork/gps-fix
updated
- 60. By Brian Douglass
-
Reverted pot file changes
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === removed file 'rockworkd/libpebble/jsfiles.qrc' |
2 | --- rockworkd/libpebble/jsfiles.qrc 2016-01-03 15:22:24 +0000 |
3 | +++ rockworkd/libpebble/jsfiles.qrc 1970-01-01 00:00:00 +0000 |
4 | @@ -1,5 +0,0 @@ |
5 | -<RCC> |
6 | - <qresource prefix="/"> |
7 | - <file>typedarray.js</file> |
8 | - </qresource> |
9 | -</RCC> |
10 | |
11 | === added directory 'rockworkd/libpebble/jskit' |
12 | === added file 'rockworkd/libpebble/jskit/cacheLocalStorage.js' |
13 | --- rockworkd/libpebble/jskit/cacheLocalStorage.js 1970-01-01 00:00:00 +0000 |
14 | +++ rockworkd/libpebble/jskit/cacheLocalStorage.js 2016-02-06 14:32:32 +0000 |
15 | @@ -0,0 +1,11 @@ |
16 | +//Since we don't have JS 6 support, this hack will allow us to save changes to localStorage when using dot or square bracket notation |
17 | + |
18 | +for (var key in localStorage) { |
19 | + _jskit.localstorage.setItem(key, localStorage.getItem(key)); |
20 | +} |
21 | + |
22 | +for (var key in _jskit.localstorage.keys()) { |
23 | + if (localStorage[key] === undefined) { |
24 | + _jskit.localstorage.removeItem(key); |
25 | + } |
26 | +} |
27 | |
28 | === added file 'rockworkd/libpebble/jskit/jsfiles.qrc' |
29 | --- rockworkd/libpebble/jskit/jsfiles.qrc 1970-01-01 00:00:00 +0000 |
30 | +++ rockworkd/libpebble/jskit/jsfiles.qrc 2016-02-06 14:32:32 +0000 |
31 | @@ -0,0 +1,7 @@ |
32 | +<RCC> |
33 | + <qresource prefix="/"> |
34 | + <file>typedarray.js</file> |
35 | + <file>jskitsetup.js</file> |
36 | + <file>cacheLocalStorage.js</file> |
37 | + </qresource> |
38 | +</RCC> |
39 | |
40 | === added file 'rockworkd/libpebble/jskit/jskitconsole.cpp' |
41 | --- rockworkd/libpebble/jskit/jskitconsole.cpp 1970-01-01 00:00:00 +0000 |
42 | +++ rockworkd/libpebble/jskit/jskitconsole.cpp 2016-02-06 14:32:32 +0000 |
43 | @@ -0,0 +1,29 @@ |
44 | +#include <QDebug> |
45 | + |
46 | +#include "jskitconsole.h" |
47 | + |
48 | +JSKitConsole::JSKitConsole(QObject *parent) : |
49 | + QObject(parent), |
50 | + l(metaObject()->className()) |
51 | +{ |
52 | +} |
53 | + |
54 | +void JSKitConsole::log(const QString &msg) |
55 | +{ |
56 | + qCDebug(l) << msg; |
57 | +} |
58 | + |
59 | +void JSKitConsole::warn(const QString &msg) |
60 | +{ |
61 | + qCWarning(l) << msg; |
62 | +} |
63 | + |
64 | +void JSKitConsole::error(const QString &msg) |
65 | +{ |
66 | + qCCritical(l) << msg; |
67 | +} |
68 | + |
69 | +void JSKitConsole::info(const QString &msg) |
70 | +{ |
71 | + qCDebug(l) << msg; |
72 | +} |
73 | |
74 | === added file 'rockworkd/libpebble/jskit/jskitconsole.h' |
75 | --- rockworkd/libpebble/jskit/jskitconsole.h 1970-01-01 00:00:00 +0000 |
76 | +++ rockworkd/libpebble/jskit/jskitconsole.h 2016-02-06 14:32:32 +0000 |
77 | @@ -0,0 +1,20 @@ |
78 | +#ifndef JSKITCONSOLE_H |
79 | +#define JSKITCONSOLE_H |
80 | + |
81 | +#include <QLoggingCategory> |
82 | + |
83 | +class JSKitConsole : public QObject |
84 | +{ |
85 | + Q_OBJECT |
86 | + QLoggingCategory l; |
87 | + |
88 | +public: |
89 | + explicit JSKitConsole(QObject *parent=0); |
90 | + |
91 | + Q_INVOKABLE void log(const QString &msg); |
92 | + Q_INVOKABLE void warn(const QString &msg); |
93 | + Q_INVOKABLE void error(const QString &msg); |
94 | + Q_INVOKABLE void info(const QString &msg); |
95 | +}; |
96 | + |
97 | +#endif // JSKITCONSOLE_H |
98 | |
99 | === added file 'rockworkd/libpebble/jskit/jskitgeolocation.cpp' |
100 | --- rockworkd/libpebble/jskit/jskitgeolocation.cpp 1970-01-01 00:00:00 +0000 |
101 | +++ rockworkd/libpebble/jskit/jskitgeolocation.cpp 2016-02-06 14:32:32 +0000 |
102 | @@ -0,0 +1,302 @@ |
103 | +#include <limits> |
104 | + |
105 | +#include "jskitgeolocation.h" |
106 | + |
107 | +JSKitGeolocation::JSKitGeolocation(QJSEngine *engine) : |
108 | + QObject(engine), |
109 | + l(metaObject()->className()), |
110 | + m_engine(engine), |
111 | + m_source(0), |
112 | + m_lastWatcherId(0) |
113 | +{ |
114 | +} |
115 | + |
116 | +void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) |
117 | +{ |
118 | + setupWatcher(successCallback, errorCallback, options, true); |
119 | +} |
120 | + |
121 | +int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) |
122 | +{ |
123 | + return setupWatcher(successCallback, errorCallback, options, false); |
124 | +} |
125 | + |
126 | +void JSKitGeolocation::clearWatch(int watcherId) |
127 | +{ |
128 | + removeWatcher(watcherId); |
129 | +} |
130 | + |
131 | +void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) |
132 | +{ |
133 | + qCWarning(l) << "positioning error: " << error; |
134 | + |
135 | + if (m_watchers.empty()) { |
136 | + qCWarning(l) << "got position error but no one is watching"; |
137 | + stopAndRemove(); |
138 | + } |
139 | + else { |
140 | + QJSValue obj; |
141 | + if (error == QGeoPositionInfoSource::AccessError) { |
142 | + obj = buildPositionErrorObject(PERMISSION_DENIED, "permission denied"); |
143 | + } else { |
144 | + obj = buildPositionErrorObject(POSITION_UNAVAILABLE, "position unavailable"); |
145 | + } |
146 | + |
147 | + for (auto it = m_watchers.begin(); it != m_watchers.end(); /*no adv*/) { |
148 | + invokeCallback(it->errorCallback, obj); |
149 | + |
150 | + if (it->once) { |
151 | + it = m_watchers.erase(it); |
152 | + } else { |
153 | + it->timer.restart(); |
154 | + ++it; |
155 | + } |
156 | + } |
157 | + } |
158 | +} |
159 | + |
160 | +void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) |
161 | +{ |
162 | + qCDebug(l) << "got position at" << pos.timestamp() << "type" << pos.coordinate().type(); |
163 | + |
164 | + if (m_watchers.empty()) { |
165 | + qCWarning(l) << "got position update but no one is watching"; |
166 | + stopAndRemove(); |
167 | + } |
168 | + else { |
169 | + QJSValue obj = buildPositionObject(pos); |
170 | + |
171 | + for (auto it = m_watchers.begin(); it != m_watchers.end(); /*no adv*/) { |
172 | + invokeCallback(it->successCallback, obj); |
173 | + |
174 | + if (it->once) { |
175 | + it = m_watchers.erase(it); |
176 | + } else { |
177 | + it->timer.restart(); |
178 | + ++it; |
179 | + } |
180 | + } |
181 | + } |
182 | +} |
183 | + |
184 | +void JSKitGeolocation::handleTimeout() |
185 | +{ |
186 | + qCDebug(l) << "positioning timeout"; |
187 | + |
188 | + if (m_watchers.empty()) { |
189 | + qCWarning(l) << "got position timeout but no one is watching"; |
190 | + stopAndRemove(); |
191 | + } |
192 | + else { |
193 | + QJSValue obj = buildPositionErrorObject(TIMEOUT, "timeout"); |
194 | + |
195 | + for (auto it = m_watchers.begin(); it != m_watchers.end(); /*no adv*/) { |
196 | + if (it->timer.hasExpired(it->timeout)) { |
197 | + qCDebug(l) << "positioning timeout for watch" << it->watcherId |
198 | + << ", watch is" << it->timer.elapsed() << "ms old, timeout is" << it->timeout; |
199 | + invokeCallback(it->errorCallback, obj); |
200 | + |
201 | + if (it->once) { |
202 | + it = m_watchers.erase(it); |
203 | + } else { |
204 | + it->timer.restart(); |
205 | + ++it; |
206 | + } |
207 | + } else { |
208 | + ++it; |
209 | + } |
210 | + } |
211 | + |
212 | + QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection); |
213 | + } |
214 | +} |
215 | + |
216 | +void JSKitGeolocation::updateTimeouts() |
217 | +{ |
218 | + int once_timeout = -1, updates_timeout = -1; |
219 | + |
220 | + Q_FOREACH(const Watcher &watcher, m_watchers) { |
221 | + qint64 rem_timeout = watcher.timeout - watcher.timer.elapsed(); |
222 | + qCDebug(l) << "watch" << watcher.watcherId << "rem timeout" << rem_timeout; |
223 | + |
224 | + if (rem_timeout >= 0) { |
225 | + // Make sure the limits aren't too large |
226 | + rem_timeout = qMin<qint64>(rem_timeout, std::numeric_limits<int>::max()); |
227 | + |
228 | + if (watcher.once) { |
229 | + once_timeout = once_timeout >= 0 ? qMin<int>(once_timeout, rem_timeout) : rem_timeout; |
230 | + } else { |
231 | + updates_timeout = updates_timeout >= 0 ? qMin<int>(updates_timeout, rem_timeout) : rem_timeout; |
232 | + } |
233 | + } |
234 | + } |
235 | + |
236 | + if (updates_timeout >= 0) { |
237 | + qCDebug(l) << "setting location update interval to" << updates_timeout; |
238 | + m_source->setUpdateInterval(updates_timeout); |
239 | + m_source->startUpdates(); |
240 | + } else { |
241 | + qCDebug(l) << "stopping updates"; |
242 | + m_source->stopUpdates(); |
243 | + } |
244 | + |
245 | + if (once_timeout >= 0) { |
246 | + qCDebug(l) << "requesting single location update with timeout" << once_timeout; |
247 | + m_source->requestUpdate(once_timeout); |
248 | + } |
249 | + |
250 | + if (once_timeout == 0 && updates_timeout == 0) { |
251 | + stopAndRemove(); |
252 | + } |
253 | +} |
254 | + |
255 | +int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once) |
256 | +{ |
257 | + Watcher watcher; |
258 | + watcher.successCallback = successCallback; |
259 | + watcher.errorCallback = errorCallback; |
260 | + watcher.highAccuracy = options.value("enableHighAccuracy", false).toBool(); |
261 | + watcher.timeout = options.value("timeout", std::numeric_limits<int>::max() - 1).toInt(); |
262 | + watcher.maximumAge = options.value("maximumAge", 0).toLongLong(); |
263 | + watcher.once = once; |
264 | + watcher.watcherId = ++m_lastWatcherId; |
265 | + |
266 | + qCDebug(l) << "setting up watcher, gps=" << watcher.highAccuracy << "timeout=" << watcher.timeout << "maximumAge=" << watcher.maximumAge << "once=" << watcher.once; |
267 | + |
268 | + if (!m_source) { |
269 | + m_source = QGeoPositionInfoSource::createDefaultSource(this); |
270 | + |
271 | + connect(m_source, static_cast<void (QGeoPositionInfoSource::*)(QGeoPositionInfoSource::Error)>(&QGeoPositionInfoSource::error), |
272 | + this, &JSKitGeolocation::handleError); |
273 | + connect(m_source, &QGeoPositionInfoSource::positionUpdated, |
274 | + this, &JSKitGeolocation::handlePosition); |
275 | + connect(m_source, &QGeoPositionInfoSource::updateTimeout, |
276 | + this, &JSKitGeolocation::handleTimeout); |
277 | + } |
278 | + |
279 | + if (watcher.maximumAge > 0) { |
280 | + QDateTime threshold = QDateTime::currentDateTime().addMSecs(-qint64(watcher.maximumAge)); |
281 | + QGeoPositionInfo pos = m_source->lastKnownPosition(watcher.highAccuracy); |
282 | + qCDebug(l) << "got pos timestamp" << pos.timestamp() << " but we want" << threshold; |
283 | + |
284 | + if (pos.isValid() && pos.timestamp() >= threshold) { |
285 | + invokeCallback(watcher.successCallback, buildPositionObject(pos)); |
286 | + |
287 | + if (once) { |
288 | + return -1; |
289 | + } |
290 | + } else if (watcher.timeout == 0 && once) { |
291 | + // If the timeout has already expired, and we have no cached data |
292 | + // Do not even bother to turn on the GPS; return error object now. |
293 | + invokeCallback(watcher.errorCallback, buildPositionErrorObject(TIMEOUT, "no cached position")); |
294 | + return -1; |
295 | + } |
296 | + } |
297 | + |
298 | + watcher.timer.start(); |
299 | + m_watchers.append(watcher); |
300 | + |
301 | + qCDebug(l) << "added new watcher" << watcher.watcherId; |
302 | + QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection); |
303 | + |
304 | + return watcher.watcherId; |
305 | +} |
306 | + |
307 | +void JSKitGeolocation::removeWatcher(int watcherId) |
308 | +{ |
309 | + Watcher watcher; |
310 | + |
311 | + qCDebug(l) << "removing watcherId" << watcher.watcherId; |
312 | + |
313 | + for (int i = 0; i < m_watchers.size(); i++) { |
314 | + if (m_watchers[i].watcherId == watcherId) { |
315 | + watcher = m_watchers.takeAt(i); |
316 | + break; |
317 | + } |
318 | + } |
319 | + |
320 | + if (watcher.watcherId != watcherId) { |
321 | + qCWarning(l) << "watcherId not found"; |
322 | + return; |
323 | + } |
324 | + |
325 | + QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection); |
326 | +} |
327 | + |
328 | +QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos) |
329 | +{ |
330 | + QJSValue obj = m_engine->newObject(); |
331 | + QJSValue coords = m_engine->newObject(); |
332 | + QJSValue timestamp = m_engine->toScriptValue<quint64>(pos.timestamp().toMSecsSinceEpoch()); |
333 | + |
334 | + coords.setProperty("latitude", m_engine->toScriptValue(pos.coordinate().latitude())); |
335 | + coords.setProperty("longitude", m_engine->toScriptValue(pos.coordinate().longitude())); |
336 | + if (pos.coordinate().type() == QGeoCoordinate::Coordinate3D) { |
337 | + coords.setProperty("altitude", m_engine->toScriptValue(pos.coordinate().altitude())); |
338 | + } else { |
339 | + coords.setProperty("altitude", m_engine->toScriptValue<void*>(0)); |
340 | + } |
341 | + |
342 | + coords.setProperty("accuracy", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::HorizontalAccuracy))); |
343 | + |
344 | + if (pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) { |
345 | + coords.setProperty("altitudeAccuracy", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::VerticalAccuracy))); |
346 | + } else { |
347 | + coords.setProperty("altitudeAccuracy", m_engine->toScriptValue<void*>(0)); |
348 | + } |
349 | + |
350 | + if (pos.hasAttribute(QGeoPositionInfo::Direction)) { |
351 | + coords.setProperty("heading", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::Direction))); |
352 | + } else { |
353 | + coords.setProperty("heading", m_engine->toScriptValue<void*>(0)); |
354 | + } |
355 | + |
356 | + if (pos.hasAttribute(QGeoPositionInfo::GroundSpeed)) { |
357 | + coords.setProperty("speed", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::GroundSpeed))); |
358 | + } else { |
359 | + coords.setProperty("speed", m_engine->toScriptValue<void*>(0)); |
360 | + } |
361 | + |
362 | + obj.setProperty("coords", coords); |
363 | + obj.setProperty("timestamp", timestamp); |
364 | + |
365 | + return obj; |
366 | +} |
367 | + |
368 | +QJSValue JSKitGeolocation::buildPositionErrorObject(PositionError error, const QString &message) |
369 | +{ |
370 | + QJSValue obj = m_engine->newObject(); |
371 | + |
372 | + obj.setProperty("code", m_engine->toScriptValue<unsigned short>(error)); |
373 | + obj.setProperty("message", m_engine->toScriptValue(message)); |
374 | + |
375 | + return obj; |
376 | +} |
377 | + |
378 | +void JSKitGeolocation::invokeCallback(QJSValue callback, QJSValue event) |
379 | +{ |
380 | + if (callback.isCallable()) { |
381 | + qCDebug(l) << "invoking callback" << callback.toString(); |
382 | + QJSValue result = callback.call(QJSValueList({event})); |
383 | + |
384 | + if (result.isError()) { |
385 | + qCWarning(l) << "error while invoking callback: " << QString("%1:%2: %3") |
386 | + .arg(result.property("fileName").toString()) |
387 | + .arg(result.property("lineNumber").toInt()) |
388 | + .arg(result.toString()); |
389 | + } |
390 | + } else { |
391 | + qCWarning(l) << "callback is not callable"; |
392 | + } |
393 | +} |
394 | + |
395 | +void JSKitGeolocation::stopAndRemove() |
396 | +{ |
397 | + if (m_source) { |
398 | + qCDebug(l) << "removing source"; |
399 | + |
400 | + m_source->stopUpdates(); |
401 | + m_source->deleteLater(); |
402 | + m_source = 0; |
403 | + } |
404 | +} |
405 | |
406 | === added file 'rockworkd/libpebble/jskit/jskitgeolocation.h' |
407 | --- rockworkd/libpebble/jskit/jskitgeolocation.h 1970-01-01 00:00:00 +0000 |
408 | +++ rockworkd/libpebble/jskit/jskitgeolocation.h 2016-02-06 14:32:32 +0000 |
409 | @@ -0,0 +1,66 @@ |
410 | +#ifndef JSKITGEOLOCATION_H |
411 | +#define JSKITGEOLOCATION_H |
412 | + |
413 | +#include <QElapsedTimer> |
414 | +#include <QGeoPositionInfoSource> |
415 | +#include <QJSValue> |
416 | +#include <QLoggingCategory> |
417 | +#include <QJSEngine> |
418 | + |
419 | +class JSKitGeolocation : public QObject |
420 | +{ |
421 | + Q_OBJECT |
422 | + QLoggingCategory l; |
423 | + |
424 | + struct Watcher; |
425 | + |
426 | +public: |
427 | + explicit JSKitGeolocation(QJSEngine *engine); |
428 | + |
429 | + enum PositionError { |
430 | + PERMISSION_DENIED = 1, |
431 | + POSITION_UNAVAILABLE = 2, |
432 | + TIMEOUT = 3 |
433 | + }; |
434 | + Q_ENUMS(PositionError); |
435 | + |
436 | + Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); |
437 | + Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); |
438 | + Q_INVOKABLE void clearWatch(int watcherId); |
439 | + |
440 | +private slots: |
441 | + void handleError(const QGeoPositionInfoSource::Error error); |
442 | + void handlePosition(const QGeoPositionInfo &pos); |
443 | + void handleTimeout(); |
444 | + void updateTimeouts(); |
445 | + |
446 | +private: |
447 | + int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once); |
448 | + void removeWatcher(int watcherId); |
449 | + |
450 | + QJSValue buildPositionObject(const QGeoPositionInfo &pos); |
451 | + QJSValue buildPositionErrorObject(PositionError error, const QString &message = QString()); |
452 | + QJSValue buildPositionErrorObject(const QGeoPositionInfoSource::Error error); |
453 | + void invokeCallback(QJSValue callback, QJSValue event); |
454 | + void stopAndRemove(); |
455 | + |
456 | +private: |
457 | + QJSEngine *m_engine; |
458 | + QGeoPositionInfoSource *m_source; |
459 | + |
460 | + struct Watcher { |
461 | + QJSValue successCallback; |
462 | + QJSValue errorCallback; |
463 | + int watcherId; |
464 | + bool once; |
465 | + bool highAccuracy; |
466 | + int timeout; |
467 | + QElapsedTimer timer; |
468 | + qlonglong maximumAge; |
469 | + }; |
470 | + |
471 | + QList<Watcher> m_watchers; |
472 | + int m_lastWatcherId; |
473 | +}; |
474 | + |
475 | +#endif // JSKITGEOLOCATION_H |
476 | |
477 | === added file 'rockworkd/libpebble/jskit/jskitlocalstorage.cpp' |
478 | --- rockworkd/libpebble/jskit/jskitlocalstorage.cpp 1970-01-01 00:00:00 +0000 |
479 | +++ rockworkd/libpebble/jskit/jskitlocalstorage.cpp 2016-02-06 14:32:32 +0000 |
480 | @@ -0,0 +1,117 @@ |
481 | +#include <QDesktopServices> |
482 | +#include <QDir> |
483 | +#include <QDebug> |
484 | + |
485 | +#include "jskitlocalstorage.h" |
486 | + |
487 | +JSKitLocalStorage::JSKitLocalStorage(QJSEngine *engine, const QString &storagePath, const QUuid &uuid): |
488 | + QObject(engine), |
489 | + m_engine(engine), |
490 | + m_storage(new QSettings(getStorageFileFor(storagePath, uuid), QSettings::IniFormat, this)) |
491 | +{ |
492 | +} |
493 | + |
494 | +int JSKitLocalStorage::length() const |
495 | +{ |
496 | + return m_storage->allKeys().size(); |
497 | +} |
498 | + |
499 | +QJSValue JSKitLocalStorage::getItem(const QJSValue &key) const |
500 | +{ |
501 | + QVariant value = m_storage->value(key.toString()); |
502 | + |
503 | + if (value.isValid()) { |
504 | + return QJSValue(value.toString()); |
505 | + } else { |
506 | + return QJSValue(QJSValue::NullValue); |
507 | + } |
508 | +} |
509 | + |
510 | +bool JSKitLocalStorage::setItem(const QJSValue &key, const QJSValue &value) |
511 | +{ |
512 | + m_storage->setValue(key.toString(), QVariant::fromValue(value.toString())); |
513 | + return true; |
514 | +} |
515 | + |
516 | +bool JSKitLocalStorage::removeItem(const QJSValue &key) |
517 | +{ |
518 | + if (m_storage->contains(key.toString())) { |
519 | + m_storage->remove(key.toString()); |
520 | + return true; |
521 | + } else { |
522 | + return false; |
523 | + } |
524 | +} |
525 | + |
526 | +void JSKitLocalStorage::clear() |
527 | +{ |
528 | + m_storage->clear(); |
529 | +} |
530 | + |
531 | +QJSValue JSKitLocalStorage::key(int index) |
532 | +{ |
533 | + QStringList allKeys = m_storage->allKeys(); |
534 | + QJSValue key(QJSValue::NullValue); |
535 | + |
536 | + if (allKeys.size() > index) { |
537 | + key = QJSValue(allKeys[index]); |
538 | + } |
539 | + |
540 | + return key; |
541 | +} |
542 | + |
543 | +QJSValue JSKitLocalStorage::get(const QJSValue &proxy, const QJSValue &key) const |
544 | +{ |
545 | + Q_UNUSED(proxy); |
546 | + return getItem(key); |
547 | +} |
548 | + |
549 | +bool JSKitLocalStorage::set(const QJSValue &proxy, const QJSValue &key, const QJSValue &value) |
550 | +{ |
551 | + Q_UNUSED(proxy); |
552 | + return setItem(key, value); |
553 | +} |
554 | + |
555 | +bool JSKitLocalStorage::has(const QJSValue &proxy, const QJSValue &key) |
556 | +{ |
557 | + Q_UNUSED(proxy); |
558 | + return m_storage->contains(key.toString()); |
559 | +} |
560 | + |
561 | +bool JSKitLocalStorage::deleteProperty(const QJSValue &proxy, const QJSValue &key) |
562 | +{ |
563 | + Q_UNUSED(proxy); |
564 | + return removeItem(key); |
565 | +} |
566 | + |
567 | +QJSValue JSKitLocalStorage::keys(const QJSValue &proxy) |
568 | +{ |
569 | + Q_UNUSED(proxy); |
570 | + |
571 | + QStringList allKeys = m_storage->allKeys(); |
572 | + QJSValue keyArray = m_engine->newArray(allKeys.size()); |
573 | + for (int i = 0; i < allKeys.size(); i++) { |
574 | + keyArray.setProperty(i, allKeys[i]); |
575 | + } |
576 | + |
577 | + return keyArray; |
578 | +} |
579 | + |
580 | +QJSValue JSKitLocalStorage::enumerate() |
581 | +{ |
582 | + return keys(0); |
583 | +} |
584 | + |
585 | +QString JSKitLocalStorage::getStorageFileFor(const QString &storageDir, const QUuid &uuid) |
586 | +{ |
587 | + QDir dataDir(storageDir + "/js-storage"); |
588 | + if (!dataDir.exists() && !dataDir.mkpath(dataDir.absolutePath())) { |
589 | + qWarning() << "Error creating jskit storage dir"; |
590 | + return QString(); |
591 | + } |
592 | + |
593 | + QString fileName = uuid.toString(); |
594 | + fileName.remove('{'); |
595 | + fileName.remove('}'); |
596 | + return dataDir.absoluteFilePath(fileName + ".ini"); |
597 | +} |
598 | |
599 | === added file 'rockworkd/libpebble/jskit/jskitlocalstorage.h' |
600 | --- rockworkd/libpebble/jskit/jskitlocalstorage.h 1970-01-01 00:00:00 +0000 |
601 | +++ rockworkd/libpebble/jskit/jskitlocalstorage.h 2016-02-06 14:32:32 +0000 |
602 | @@ -0,0 +1,40 @@ |
603 | +#ifndef JSKITLOCALSTORAGE_P_H |
604 | +#define JSKITLOCALSTORAGE_P_H |
605 | + |
606 | +#include <QSettings> |
607 | +#include <QJSEngine> |
608 | +#include <QUuid> |
609 | + |
610 | +class JSKitLocalStorage : public QObject |
611 | +{ |
612 | + Q_OBJECT |
613 | + |
614 | + Q_PROPERTY(int length READ length) |
615 | + |
616 | +public: |
617 | + explicit JSKitLocalStorage(QJSEngine *engine, const QString &storagePath, const QUuid &uuid); |
618 | + |
619 | + int length() const; |
620 | + |
621 | + Q_INVOKABLE QJSValue getItem(const QJSValue &key) const; |
622 | + Q_INVOKABLE bool setItem(const QJSValue &key, const QJSValue &value); |
623 | + Q_INVOKABLE bool removeItem(const QJSValue &key); |
624 | + Q_INVOKABLE void clear(); |
625 | + Q_INVOKABLE QJSValue key(int index); |
626 | + |
627 | + Q_INVOKABLE QJSValue get(const QJSValue &proxy, const QJSValue &key) const; |
628 | + Q_INVOKABLE bool set(const QJSValue &proxy, const QJSValue &key, const QJSValue &value); |
629 | + Q_INVOKABLE bool has(const QJSValue &proxy, const QJSValue &key); |
630 | + Q_INVOKABLE bool deleteProperty(const QJSValue &proxy, const QJSValue &key); |
631 | + Q_INVOKABLE QJSValue keys(const QJSValue &proxy=0); |
632 | + Q_INVOKABLE QJSValue enumerate(); |
633 | + |
634 | +private: |
635 | + static QString getStorageFileFor(const QString &storageDir, const QUuid &uuid); |
636 | + |
637 | +private: |
638 | + QJSEngine *m_engine; |
639 | + QSettings *m_storage; |
640 | +}; |
641 | + |
642 | +#endif // JSKITLOCALSTORAGE_P_H |
643 | |
644 | === added file 'rockworkd/libpebble/jskit/jskitmanager.cpp' |
645 | --- rockworkd/libpebble/jskit/jskitmanager.cpp 1970-01-01 00:00:00 +0000 |
646 | +++ rockworkd/libpebble/jskit/jskitmanager.cpp 2016-02-06 14:32:32 +0000 |
647 | @@ -0,0 +1,240 @@ |
648 | +#include <QFile> |
649 | +#include <QDir> |
650 | + |
651 | +#include "jskitmanager.h" |
652 | +#include "jskitpebble.h" |
653 | + |
654 | +JSKitManager::JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent) : |
655 | + QObject(parent), |
656 | + l(metaObject()->className()), |
657 | + m_pebble(pebble), |
658 | + m_connection(connection), |
659 | + m_apps(apps), |
660 | + m_appmsg(appmsg), |
661 | + m_engine(0), |
662 | + m_configurationUuid(0) |
663 | +{ |
664 | + connect(m_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); |
665 | + connect(m_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); |
666 | +} |
667 | + |
668 | +JSKitManager::~JSKitManager() |
669 | +{ |
670 | + if (m_engine) { |
671 | + stopJsApp(); |
672 | + } |
673 | +} |
674 | + |
675 | +QJSEngine * JSKitManager::engine() |
676 | +{ |
677 | + return m_engine; |
678 | +} |
679 | + |
680 | +bool JSKitManager::isJSKitAppRunning() const |
681 | +{ |
682 | + return m_engine != 0; |
683 | +} |
684 | + |
685 | +QString JSKitManager::describeError(QJSValue error) |
686 | +{ |
687 | + return QString("%1:%2: %3") |
688 | + .arg(error.property("fileName").toString()) |
689 | + .arg(error.property("lineNumber").toInt()) |
690 | + .arg(error.toString()); |
691 | +} |
692 | + |
693 | +void JSKitManager::showConfiguration() |
694 | +{ |
695 | + if (m_engine) { |
696 | + qCDebug(l) << "requesting configuration"; |
697 | + m_jspebble->invokeCallbacks("showConfiguration"); |
698 | + } else { |
699 | + qCWarning(l) << "requested to show configuration, but JS engine is not running"; |
700 | + } |
701 | +} |
702 | + |
703 | +void JSKitManager::handleWebviewClosed(const QString &result) |
704 | +{ |
705 | + if (m_engine) { |
706 | + QJSValue eventObj = m_engine->newObject(); |
707 | + QByteArray data = QByteArray::fromPercentEncoding(result.toUtf8()); |
708 | + eventObj.setProperty("response", m_engine->toScriptValue(data)); |
709 | + |
710 | + qCDebug(l) << "Sending" << eventObj.property("response").toString(); |
711 | + m_jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj})); |
712 | + |
713 | + loadJsFile(":/cacheLocalStorage.js"); |
714 | + } else { |
715 | + qCWarning(l) << "webview closed event, but JS engine is not running"; |
716 | + } |
717 | +} |
718 | + |
719 | +void JSKitManager::setConfigurationId(const QUuid &uuid) |
720 | +{ |
721 | + m_configurationUuid = uuid; |
722 | +} |
723 | + |
724 | +AppInfo JSKitManager::currentApp() |
725 | +{ |
726 | + return m_curApp; |
727 | +} |
728 | + |
729 | +void JSKitManager::handleAppStarted(const QUuid &uuid) |
730 | +{ |
731 | + AppInfo info = m_apps->info(uuid); |
732 | + if (!info.uuid().isNull() && info.isJSKit()) { |
733 | + qCDebug(l) << "Preparing to start JSKit app" << info.uuid() << info.shortName(); |
734 | + |
735 | + m_curApp = info; |
736 | + startJsApp(); |
737 | + } |
738 | +} |
739 | + |
740 | +void JSKitManager::handleAppStopped(const QUuid &uuid) |
741 | +{ |
742 | + if (!m_curApp.uuid().isNull()) { |
743 | + if (m_curApp.uuid() != uuid) { |
744 | + qCWarning(l) << "Closed app with invalid UUID"; |
745 | + } |
746 | + |
747 | + stopJsApp(); |
748 | + m_curApp = AppInfo(); |
749 | + qCDebug(l) << "App stopped" << uuid; |
750 | + } |
751 | +} |
752 | + |
753 | +void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &msg) |
754 | +{ |
755 | + if (m_curApp.uuid() == uuid) { |
756 | + qCDebug(l) << "handling app message" << uuid << msg; |
757 | + |
758 | + if (m_engine) { |
759 | + QJSValue eventObj = m_engine->newObject(); |
760 | + eventObj.setProperty("payload", m_engine->toScriptValue(msg)); |
761 | + |
762 | + m_jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj})); |
763 | + |
764 | + loadJsFile(":/cacheLocalStorage.js"); |
765 | + } |
766 | + else { |
767 | + qCDebug(l) << "but engine is stopped"; |
768 | + } |
769 | + } |
770 | +} |
771 | + |
772 | +bool JSKitManager::loadJsFile(const QString &filename) |
773 | +{ |
774 | + Q_ASSERT(m_engine); |
775 | + |
776 | + QFile file(filename); |
777 | + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { |
778 | + qCWarning(l) << "Failed to load JS file:" << file.fileName(); |
779 | + return false; |
780 | + } |
781 | + |
782 | + qCDebug(l) << "evaluating js file" << file.fileName(); |
783 | + |
784 | + QJSValue result = m_engine->evaluate(QString::fromUtf8(file.readAll()), file.fileName()); |
785 | + if (result.isError()) { |
786 | + qCWarning(l) << "error while evaluating JS script:" << describeError(result); |
787 | + return false; |
788 | + } |
789 | + |
790 | + qCDebug(l) << "JS script evaluated"; |
791 | + return true; |
792 | +} |
793 | + |
794 | +void JSKitManager::startJsApp() |
795 | +{ |
796 | + if (m_engine) stopJsApp(); |
797 | + |
798 | + if (m_curApp.uuid().isNull()) { |
799 | + qCWarning(l) << "Attempting to start JS app with invalid UUID"; |
800 | + return; |
801 | + } |
802 | + |
803 | + m_engine = new QJSEngine(this); |
804 | + m_jspebble = new JSKitPebble(m_curApp, this, m_engine); |
805 | + m_jsconsole = new JSKitConsole(m_engine); |
806 | + m_jsstorage = new JSKitLocalStorage(m_engine, m_pebble->storagePath(), m_curApp.uuid()); |
807 | + m_jsgeo = new JSKitGeolocation(m_engine); |
808 | + m_jstimer = new JSKitTimer(m_engine); |
809 | + m_jsperformance = new JSKitPerformance(m_engine); |
810 | + |
811 | + qCDebug(l) << "starting JS app" << m_curApp.shortName(); |
812 | + |
813 | + QJSValue globalObj = m_engine->globalObject(); |
814 | + QJSValue jskitObj = m_engine->newObject(); |
815 | + |
816 | + jskitObj.setProperty("pebble", m_engine->newQObject(m_jspebble)); |
817 | + jskitObj.setProperty("console", m_engine->newQObject(m_jsconsole)); |
818 | + jskitObj.setProperty("localstorage", m_engine->newQObject(m_jsstorage)); |
819 | + jskitObj.setProperty("geolocation", m_engine->newQObject(m_jsgeo)); |
820 | + jskitObj.setProperty("timer", m_engine->newQObject(m_jstimer)); |
821 | + jskitObj.setProperty("performance", m_engine->newQObject(m_jsperformance)); |
822 | + globalObj.setProperty("_jskit", jskitObj); |
823 | + |
824 | + QJSValue navigatorObj = m_engine->newObject(); |
825 | + navigatorObj.setProperty("language", m_engine->toScriptValue(QLocale().name())); |
826 | + globalObj.setProperty("navigator", navigatorObj); |
827 | + |
828 | + // Set this.window = this |
829 | + globalObj.setProperty("window", globalObj); |
830 | + |
831 | + // Shims for compatibility... |
832 | + loadJsFile(":/jskitsetup.js"); |
833 | + |
834 | + // Polyfills... |
835 | + loadJsFile(":/typedarray.js"); |
836 | + |
837 | + // Now the actual script |
838 | + QString jsApp = m_curApp.file(AppInfo::FileTypeJsApp, HardwarePlatformUnknown); |
839 | + QFile f(jsApp); |
840 | + if (!f.open(QFile::ReadOnly)) { |
841 | + qCWarning(l) << "Error opening" << jsApp; |
842 | + return; |
843 | + } |
844 | + QJSValue ret = m_engine->evaluate(QString::fromUtf8(f.readAll())); |
845 | + qCDebug(l) << "loaded script" << ret.toString(); |
846 | + |
847 | + // Setup the message callback |
848 | + QUuid uuid = m_curApp.uuid(); |
849 | + m_appmsg->setMessageHandler(uuid, [this, uuid](const QVariantMap &msg) { |
850 | + QMetaObject::invokeMethod(this, "handleAppMessage", Qt::QueuedConnection, |
851 | + Q_ARG(QUuid, uuid), |
852 | + Q_ARG(QVariantMap, msg)); |
853 | + |
854 | + // Invoke the slot as a queued connection to give time for the ACK message |
855 | + // to go through first. |
856 | + |
857 | + return true; |
858 | + }); |
859 | + |
860 | + // We try to invoke the callbacks even if script parsing resulted in error... |
861 | + m_jspebble->invokeCallbacks("ready"); |
862 | + |
863 | + loadJsFile(":/cacheLocalStorage.js"); |
864 | + |
865 | + if (m_configurationUuid == m_curApp.uuid()) { |
866 | + qCDebug(l) << "going to launch config for" << m_configurationUuid; |
867 | + showConfiguration(); |
868 | + } |
869 | + |
870 | + m_configurationUuid = QUuid(); |
871 | +} |
872 | + |
873 | +void JSKitManager::stopJsApp() |
874 | +{ |
875 | + qCDebug(l) << "stop js app" << m_curApp.uuid(); |
876 | + if (!m_engine) return; // Nothing to do! |
877 | + |
878 | + loadJsFile(":/cacheLocalStorage.js"); |
879 | + |
880 | + if (!m_curApp.uuid().isNull()) { |
881 | + m_appmsg->clearMessageHandler(m_curApp.uuid()); |
882 | + } |
883 | + |
884 | + m_engine->collectGarbage(); |
885 | + m_engine->deleteLater(); |
886 | + m_engine = 0; |
887 | +} |
888 | |
889 | === added file 'rockworkd/libpebble/jskit/jskitmanager.h' |
890 | --- rockworkd/libpebble/jskit/jskitmanager.h 1970-01-01 00:00:00 +0000 |
891 | +++ rockworkd/libpebble/jskit/jskitmanager.h 2016-02-06 14:32:32 +0000 |
892 | @@ -0,0 +1,72 @@ |
893 | +#ifndef JSKITMANAGER_H |
894 | +#define JSKITMANAGER_H |
895 | + |
896 | +#include <QJSEngine> |
897 | +#include <QPointer> |
898 | +#include <QLoggingCategory> |
899 | + |
900 | +#include "../appmanager.h" |
901 | +#include "../watchconnection.h" |
902 | +#include "../pebble.h" |
903 | +#include "../appmsgmanager.h" |
904 | + |
905 | +#include "jskitconsole.h" |
906 | +#include "jskitgeolocation.h" |
907 | +#include "jskitlocalstorage.h" |
908 | +#include "jskittimer.h" |
909 | +#include "jskitperformance.h" |
910 | + |
911 | +class JSKitPebble; |
912 | + |
913 | +class JSKitManager : public QObject |
914 | +{ |
915 | + Q_OBJECT |
916 | + QLoggingCategory l; |
917 | + |
918 | +public: |
919 | + explicit JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); |
920 | + ~JSKitManager(); |
921 | + |
922 | + QJSEngine * engine(); |
923 | + bool isJSKitAppRunning() const; |
924 | + |
925 | + static QString describeError(QJSValue error); |
926 | + |
927 | + void showConfiguration(); |
928 | + void handleWebviewClosed(const QString &result); |
929 | + void setConfigurationId(const QUuid &uuid); |
930 | + AppInfo currentApp(); |
931 | + |
932 | +signals: |
933 | + void appNotification(const QUuid &uuid, const QString &title, const QString &body); |
934 | + void openURL(const QString &uuid, const QString &url); |
935 | + |
936 | +private slots: |
937 | + void handleAppStarted(const QUuid &uuid); |
938 | + void handleAppStopped(const QUuid &uuid); |
939 | + void handleAppMessage(const QUuid &uuid, const QVariantMap &msg); |
940 | + |
941 | +private: |
942 | + bool loadJsFile(const QString &filename); |
943 | + void startJsApp(); |
944 | + void stopJsApp(); |
945 | + |
946 | +private: |
947 | + friend class JSKitPebble; |
948 | + |
949 | + Pebble *m_pebble; |
950 | + WatchConnection *m_connection; |
951 | + AppManager *m_apps; |
952 | + AppMsgManager *m_appmsg; |
953 | + AppInfo m_curApp; |
954 | + QJSEngine *m_engine; |
955 | + QPointer<JSKitPebble> m_jspebble; |
956 | + QPointer<JSKitConsole> m_jsconsole; |
957 | + QPointer<JSKitLocalStorage> m_jsstorage; |
958 | + QPointer<JSKitGeolocation> m_jsgeo; |
959 | + QPointer<JSKitTimer> m_jstimer; |
960 | + QPointer<JSKitPerformance> m_jsperformance; |
961 | + QUuid m_configurationUuid; |
962 | +}; |
963 | + |
964 | +#endif // JSKITMANAGER_H |
965 | |
966 | === added file 'rockworkd/libpebble/jskit/jskitpebble.cpp' |
967 | --- rockworkd/libpebble/jskit/jskitpebble.cpp 1970-01-01 00:00:00 +0000 |
968 | +++ rockworkd/libpebble/jskit/jskitpebble.cpp 2016-02-06 14:32:32 +0000 |
969 | @@ -0,0 +1,355 @@ |
970 | +#include <QUrl> |
971 | +#include <QCryptographicHash> |
972 | +#include <QSettings> |
973 | + |
974 | +#include "jskitpebble.h" |
975 | +#include "jskitxmlhttprequest.h" |
976 | + |
977 | +static const char *token_salt = "0feeb7416d3c4546a19b04bccd8419b1"; |
978 | + |
979 | +JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr, QObject *parent) : |
980 | + QObject(parent), |
981 | + l(metaObject()->className()), |
982 | + m_appInfo(info), |
983 | + m_mgr(mgr) |
984 | +{ |
985 | +} |
986 | + |
987 | +void JSKitPebble::addEventListener(const QString &type, QJSValue function) |
988 | +{ |
989 | + m_listeners[type].append(function); |
990 | +} |
991 | + |
992 | +void JSKitPebble::removeEventListener(const QString &type, QJSValue function) |
993 | +{ |
994 | + if (!m_listeners.contains(type)) return; |
995 | + |
996 | + QList<QJSValue> &callbacks = m_listeners[type]; |
997 | + for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ) { |
998 | + if (it->strictlyEquals(function)) { |
999 | + it = callbacks.erase(it); |
1000 | + } else { |
1001 | + ++it; |
1002 | + } |
1003 | + } |
1004 | + |
1005 | + if (callbacks.empty()) { |
1006 | + m_listeners.remove(type); |
1007 | + } |
1008 | +} |
1009 | + |
1010 | +void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body) |
1011 | +{ |
1012 | + qCDebug(l) << "showSimpleNotificationOnPebble" << title << body; |
1013 | + emit m_mgr->appNotification(m_appInfo.uuid(), title, body); |
1014 | +} |
1015 | + |
1016 | +uint JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) |
1017 | +{ |
1018 | + QVariantMap data = message.toVariant().toMap(); |
1019 | + QPointer<JSKitPebble> pebbObj = this; |
1020 | + uint transactionId = m_mgr->m_appmsg->nextTransactionId(); |
1021 | + |
1022 | + qCDebug(l) << "sendAppMessage" << data; |
1023 | + |
1024 | + m_mgr->m_appmsg->send( |
1025 | + m_appInfo.uuid(), |
1026 | + data, |
1027 | + [this, pebbObj, transactionId, callbackForAck]() mutable { |
1028 | + if (pebbObj.isNull()) return; |
1029 | + |
1030 | + if (callbackForAck.isCallable()) { |
1031 | + QJSValue event = pebbObj->buildAckEventObject(transactionId); |
1032 | + QJSValue result = callbackForAck.call(QJSValueList({event})); |
1033 | + |
1034 | + if (result.isError()) { |
1035 | + qCWarning(l) << "error while invoking ACK callback" |
1036 | + << callbackForAck.toString() << ":" |
1037 | + << JSKitManager::describeError(result); |
1038 | + } |
1039 | + } |
1040 | + }, |
1041 | + [this, pebbObj, transactionId, callbackForNack]() mutable { |
1042 | + if (pebbObj.isNull()) return; |
1043 | + |
1044 | + if (callbackForNack.isCallable()) { |
1045 | + QJSValue event = pebbObj->buildAckEventObject(transactionId, "NACK from watch"); |
1046 | + QJSValue result = callbackForNack.call(QJSValueList({event})); |
1047 | + |
1048 | + if (result.isError()) { |
1049 | + qCWarning(l) << "error while invoking NACK callback" |
1050 | + << callbackForNack.toString() << ":" |
1051 | + << JSKitManager::describeError(result); |
1052 | + } |
1053 | + } |
1054 | + } |
1055 | + ); |
1056 | + |
1057 | + return transactionId; |
1058 | +} |
1059 | + |
1060 | +void JSKitPebble::getTimelineToken(QJSValue successCallback, QJSValue failureCallback) |
1061 | +{ |
1062 | + //TODO actually implement this |
1063 | + qCDebug(l) << "call to unsupported method Pebble.getTimelineToken"; |
1064 | + Q_UNUSED(successCallback); |
1065 | + |
1066 | + if (failureCallback.isCallable()) { |
1067 | + failureCallback.call(); |
1068 | + } |
1069 | +} |
1070 | + |
1071 | +void JSKitPebble::timelineSubscribe(const QString &topic, QJSValue successCallback, QJSValue failureCallback) |
1072 | +{ |
1073 | + //TODO actually implement this |
1074 | + qCDebug(l) << "call to unsupported method Pebble.timelineSubscribe"; |
1075 | + Q_UNUSED(topic); |
1076 | + Q_UNUSED(successCallback); |
1077 | + |
1078 | + if (failureCallback.isCallable()) { |
1079 | + failureCallback.call(); |
1080 | + } |
1081 | +} |
1082 | + |
1083 | +void JSKitPebble::timelineUnsubscribe(const QString &topic, QJSValue successCallback, QJSValue failureCallback) |
1084 | +{ |
1085 | + //TODO actually implement this |
1086 | + qCDebug(l) << "call to unsupported method Pebble.timelineUnsubscribe"; |
1087 | + Q_UNUSED(topic); |
1088 | + Q_UNUSED(successCallback); |
1089 | + |
1090 | + if (failureCallback.isCallable()) { |
1091 | + failureCallback.call(); |
1092 | + } |
1093 | +} |
1094 | + |
1095 | +void JSKitPebble::timelineSubscriptions(QJSValue successCallback, QJSValue failureCallback) |
1096 | +{ |
1097 | + //TODO actually implement this |
1098 | + qCDebug(l) << "call to unsupported method Pebble.timelineSubscriptions"; |
1099 | + Q_UNUSED(successCallback); |
1100 | + |
1101 | + if (failureCallback.isCallable()) { |
1102 | + failureCallback.call(); |
1103 | + } |
1104 | +} |
1105 | + |
1106 | + |
1107 | +QString JSKitPebble::getAccountToken() const |
1108 | +{ |
1109 | + // We do not have any account system, so we just fake something up. |
1110 | + QCryptographicHash hasher(QCryptographicHash::Md5); |
1111 | + |
1112 | + hasher.addData(token_salt, strlen(token_salt)); |
1113 | + hasher.addData(m_appInfo.uuid().toByteArray()); |
1114 | + |
1115 | + QSettings settings; |
1116 | + QString token = settings.value("accountToken").toString(); |
1117 | + |
1118 | + if (token.isEmpty()) { |
1119 | + token = QUuid::createUuid().toString(); |
1120 | + qCDebug(l) << "created new account token" << token; |
1121 | + settings.setValue("accountToken", token); |
1122 | + } |
1123 | + |
1124 | + hasher.addData(token.toLatin1()); |
1125 | + |
1126 | + QString hash = hasher.result().toHex(); |
1127 | + qCDebug(l) << "returning account token" << hash; |
1128 | + |
1129 | + return hash; |
1130 | +} |
1131 | + |
1132 | +QString JSKitPebble::getWatchToken() const |
1133 | +{ |
1134 | + QCryptographicHash hasher(QCryptographicHash::Md5); |
1135 | + |
1136 | + hasher.addData(token_salt, strlen(token_salt)); |
1137 | + hasher.addData(m_appInfo.uuid().toByteArray()); |
1138 | + hasher.addData(m_mgr->m_pebble->serialNumber().toLatin1()); |
1139 | + |
1140 | + QString hash = hasher.result().toHex(); |
1141 | + qCDebug(l) << "returning watch token" << hash; |
1142 | + |
1143 | + return hash; |
1144 | +} |
1145 | + |
1146 | +QJSValue JSKitPebble::getActiveWatchInfo() const |
1147 | +{ |
1148 | + QJSValue watchInfo = m_mgr->m_engine->newObject(); |
1149 | + |
1150 | + switch (m_mgr->m_pebble->hardwarePlatform()) { |
1151 | + case HardwarePlatformBasalt: |
1152 | + watchInfo.setProperty("platform", "basalt"); |
1153 | + break; |
1154 | + |
1155 | + case HardwarePlatformChalk: |
1156 | + watchInfo.setProperty("platform", "chalk"); |
1157 | + break; |
1158 | + |
1159 | + default: |
1160 | + watchInfo.setProperty("platform", "aplite"); |
1161 | + break; |
1162 | + } |
1163 | + |
1164 | + switch (m_mgr->m_pebble->model()) { |
1165 | + case ModelTintinWhite: |
1166 | + watchInfo.setProperty("model", "pebble_white"); |
1167 | + break; |
1168 | + |
1169 | + case ModelTintinRed: |
1170 | + watchInfo.setProperty("model", "pebble_red"); |
1171 | + break; |
1172 | + |
1173 | + case ModelTintinOrange: |
1174 | + watchInfo.setProperty("model", "pebble_orange"); |
1175 | + break; |
1176 | + |
1177 | + case ModelTintinGrey: |
1178 | + watchInfo.setProperty("model", "pebble_grey"); |
1179 | + break; |
1180 | + |
1181 | + case ModelBiancaSilver: |
1182 | + watchInfo.setProperty("model", "pebble_steel_silver"); |
1183 | + break; |
1184 | + |
1185 | + case ModelBiancaBlack: |
1186 | + watchInfo.setProperty("model", "pebble_steel_black"); |
1187 | + break; |
1188 | + |
1189 | + case ModelTintinBlue: |
1190 | + watchInfo.setProperty("model", "pebble_blue"); |
1191 | + break; |
1192 | + |
1193 | + case ModelTintinGreen: |
1194 | + watchInfo.setProperty("model", "pebble_green"); |
1195 | + break; |
1196 | + |
1197 | + case ModelTintinPink: |
1198 | + watchInfo.setProperty("model", "pebble_pink"); |
1199 | + break; |
1200 | + |
1201 | + case ModelSnowyWhite: |
1202 | + watchInfo.setProperty("model", "pebble_time_white"); |
1203 | + break; |
1204 | + |
1205 | + case ModelSnowyBlack: |
1206 | + watchInfo.setProperty("model", "pebble_time_black"); |
1207 | + break; |
1208 | + |
1209 | + case ModelSnowyRed: |
1210 | + watchInfo.setProperty("model", "pebble_time_read"); |
1211 | + break; |
1212 | + |
1213 | + case ModelBobbySilver: |
1214 | + watchInfo.setProperty("model", "pebble_time_steel_silver"); |
1215 | + break; |
1216 | + |
1217 | + case ModelBobbyBlack: |
1218 | + watchInfo.setProperty("model", "pebble_time_steel_black"); |
1219 | + break; |
1220 | + |
1221 | + case ModelBobbyGold: |
1222 | + watchInfo.setProperty("model", "pebble_time_steel_gold"); |
1223 | + break; |
1224 | + |
1225 | + case ModelSpalding14Silver: |
1226 | + watchInfo.setProperty("model", "pebble_time_round_silver_14mm"); |
1227 | + break; |
1228 | + |
1229 | + case ModelSpalding14Black: |
1230 | + watchInfo.setProperty("model", "pebble_time_round_black_14mm"); |
1231 | + break; |
1232 | + |
1233 | + case ModelSpalding20Silver: |
1234 | + watchInfo.setProperty("model", "pebble_time_round_silver_20mm"); |
1235 | + break; |
1236 | + |
1237 | + case ModelSpalding20Black: |
1238 | + watchInfo.setProperty("model", "pebble_time_round_black_20mm"); |
1239 | + break; |
1240 | + |
1241 | + case ModelSpalding14RoseGold: |
1242 | + watchInfo.setProperty("model", "pebble_time_round_rose_gold_14mm"); |
1243 | + break; |
1244 | + |
1245 | + default: |
1246 | + watchInfo.setProperty("model", "pebble_black"); |
1247 | + break; |
1248 | + } |
1249 | + |
1250 | + watchInfo.setProperty("language", m_mgr->m_pebble->language()); |
1251 | + |
1252 | + QJSValue firmware = m_mgr->m_engine->newObject(); |
1253 | + QString version = m_mgr->m_pebble->softwareVersion().remove("v"); |
1254 | + QStringList versionParts = version.split("."); |
1255 | + |
1256 | + if (versionParts.count() >= 1) { |
1257 | + firmware.setProperty("major", versionParts[0].toInt()); |
1258 | + } |
1259 | + |
1260 | + if (versionParts.count() >= 2) { |
1261 | + firmware.setProperty("minor", versionParts[1].toInt()); |
1262 | + } |
1263 | + |
1264 | + if (versionParts.count() >= 3) { |
1265 | + if (versionParts[2].contains("-")) { |
1266 | + QStringList patchParts = version.split("-"); |
1267 | + firmware.setProperty("patch", patchParts[0].toInt()); |
1268 | + firmware.setProperty("suffix", patchParts[1]); |
1269 | + } else { |
1270 | + firmware.setProperty("patch", versionParts[2].toInt()); |
1271 | + firmware.setProperty("suffix", ""); |
1272 | + } |
1273 | + } |
1274 | + |
1275 | + watchInfo.setProperty("firmware", firmware); |
1276 | + return watchInfo; |
1277 | +} |
1278 | + |
1279 | +void JSKitPebble::openURL(const QUrl &url) |
1280 | +{ |
1281 | + emit m_mgr->openURL(m_appInfo.uuid().toString(), url.toString()); |
1282 | +} |
1283 | + |
1284 | +QJSValue JSKitPebble::createXMLHttpRequest() |
1285 | +{ |
1286 | + JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(m_mgr->engine()); |
1287 | + // Should be deleted by JS engine. |
1288 | + return m_mgr->engine()->newQObject(xhr); |
1289 | +} |
1290 | + |
1291 | +QJSValue JSKitPebble::buildAckEventObject(uint transaction, const QString &message) const |
1292 | +{ |
1293 | + QJSEngine *engine = m_mgr->engine(); |
1294 | + QJSValue eventObj = engine->newObject(); |
1295 | + QJSValue dataObj = engine->newObject(); |
1296 | + |
1297 | + dataObj.setProperty("transactionId", engine->toScriptValue(transaction)); |
1298 | + eventObj.setProperty("data", dataObj); |
1299 | + |
1300 | + if (!message.isEmpty()) { |
1301 | + QJSValue errorObj = engine->newObject(); |
1302 | + |
1303 | + errorObj.setProperty("message", engine->toScriptValue(message)); |
1304 | + eventObj.setProperty("error", errorObj); |
1305 | + } |
1306 | + |
1307 | + return eventObj; |
1308 | +} |
1309 | + |
1310 | +void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) |
1311 | +{ |
1312 | + if (!m_listeners.contains(type)) return; |
1313 | + QList<QJSValue> &callbacks = m_listeners[type]; |
1314 | + |
1315 | + for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ++it) { |
1316 | + qCDebug(l) << "invoking callback" << type << it->toString(); |
1317 | + QJSValue result = it->call(args); |
1318 | + if (result.isError()) { |
1319 | + qCWarning(l) << "error while invoking callback" |
1320 | + << type << it->toString() << ":" |
1321 | + << JSKitManager::describeError(result); |
1322 | + } |
1323 | + } |
1324 | +} |
1325 | |
1326 | === added file 'rockworkd/libpebble/jskit/jskitpebble.h' |
1327 | --- rockworkd/libpebble/jskit/jskitpebble.h 1970-01-01 00:00:00 +0000 |
1328 | +++ rockworkd/libpebble/jskit/jskitpebble.h 2016-02-06 14:32:32 +0000 |
1329 | @@ -0,0 +1,47 @@ |
1330 | +#ifndef JSKITPEBBLE_P_H |
1331 | +#define JSKITPEBBLE_P_H |
1332 | + |
1333 | +#include <QLoggingCategory> |
1334 | + |
1335 | +#include "jskitmanager.h" |
1336 | +#include "../appinfo.h" |
1337 | + |
1338 | +class JSKitPebble : public QObject |
1339 | +{ |
1340 | + Q_OBJECT |
1341 | + QLoggingCategory l; |
1342 | + |
1343 | +public: |
1344 | + explicit JSKitPebble(const AppInfo &appInfo, JSKitManager *mgr, QObject *parent=0); |
1345 | + |
1346 | + Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); |
1347 | + Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); |
1348 | + |
1349 | + Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body); |
1350 | + Q_INVOKABLE uint sendAppMessage(QJSValue message, QJSValue callbackForAck = QJSValue(), QJSValue callbackForNack = QJSValue()); |
1351 | + |
1352 | + Q_INVOKABLE void getTimelineToken(QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue()); |
1353 | + Q_INVOKABLE void timelineSubscribe(const QString &topic, QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue()); |
1354 | + Q_INVOKABLE void timelineUnsubscribe(const QString &topic, QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue()); |
1355 | + Q_INVOKABLE void timelineSubscriptions(QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue()); |
1356 | + |
1357 | + Q_INVOKABLE QString getAccountToken() const; |
1358 | + Q_INVOKABLE QString getWatchToken() const; |
1359 | + Q_INVOKABLE QJSValue getActiveWatchInfo() const; |
1360 | + |
1361 | + Q_INVOKABLE void openURL(const QUrl &url); |
1362 | + |
1363 | + Q_INVOKABLE QJSValue createXMLHttpRequest(); |
1364 | + |
1365 | + void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); |
1366 | + |
1367 | +private: |
1368 | + QJSValue buildAckEventObject(uint transaction, const QString &message = QString()) const; |
1369 | + |
1370 | +private: |
1371 | + AppInfo m_appInfo; |
1372 | + JSKitManager *m_mgr; |
1373 | + QHash<QString, QList<QJSValue>> m_listeners; |
1374 | +}; |
1375 | + |
1376 | +#endif // JSKITPEBBLE_P_H |
1377 | |
1378 | === added file 'rockworkd/libpebble/jskit/jskitperformance.cpp' |
1379 | --- rockworkd/libpebble/jskit/jskitperformance.cpp 1970-01-01 00:00:00 +0000 |
1380 | +++ rockworkd/libpebble/jskit/jskitperformance.cpp 2016-02-06 14:32:32 +0000 |
1381 | @@ -0,0 +1,13 @@ |
1382 | +#include "jskitperformance.h" |
1383 | + |
1384 | +JSKitPerformance::JSKitPerformance(QObject *parent) : |
1385 | + QObject(parent), |
1386 | + m_start(QTime::currentTime()) |
1387 | +{ |
1388 | +} |
1389 | + |
1390 | +int JSKitPerformance::now() |
1391 | +{ |
1392 | + QTime now = QTime::currentTime(); |
1393 | + return m_start.msecsTo(now); |
1394 | +} |
1395 | |
1396 | === added file 'rockworkd/libpebble/jskit/jskitperformance.h' |
1397 | --- rockworkd/libpebble/jskit/jskitperformance.h 1970-01-01 00:00:00 +0000 |
1398 | +++ rockworkd/libpebble/jskit/jskitperformance.h 2016-02-06 14:32:32 +0000 |
1399 | @@ -0,0 +1,20 @@ |
1400 | +#ifndef JSKITPERFORMANCE_H |
1401 | +#define JSKITPERFORMANCE_H |
1402 | + |
1403 | +#include <QObject> |
1404 | +#include <QTime> |
1405 | + |
1406 | +class JSKitPerformance : public QObject |
1407 | +{ |
1408 | + Q_OBJECT |
1409 | + |
1410 | +public: |
1411 | + explicit JSKitPerformance(QObject *parent=0); |
1412 | + |
1413 | + Q_INVOKABLE int now(); |
1414 | + |
1415 | +private: |
1416 | + QTime m_start; |
1417 | +}; |
1418 | + |
1419 | +#endif // JSKITPERFORMANCE_H |
1420 | |
1421 | === added file 'rockworkd/libpebble/jskit/jskitsetup.js' |
1422 | --- rockworkd/libpebble/jskit/jskitsetup.js 1970-01-01 00:00:00 +0000 |
1423 | +++ rockworkd/libpebble/jskit/jskitsetup.js 2016-02-06 14:32:32 +0000 |
1424 | @@ -0,0 +1,191 @@ |
1425 | +//Borrowed from https://github.com/pebble/pypkjs/blob/master/pypkjs/javascript/runtime.py#L17 |
1426 | +_jskit.make_proxies = function(proxy, origin, names) { |
1427 | + names.forEach(function(name) { |
1428 | + proxy[name] = eval("(function " + name + "() { return origin[name].apply(origin, arguments); })"); |
1429 | + }); |
1430 | + |
1431 | + return proxy; |
1432 | +} |
1433 | + |
1434 | +_jskit.make_properties = function(proxy, origin, names) { |
1435 | + names.forEach(function(name) { |
1436 | + Object.defineProperty(proxy, name, { |
1437 | + configurable: false, |
1438 | + enumerable: true, |
1439 | + get: function() { |
1440 | + return origin[name]; |
1441 | + }, |
1442 | + set: function(value) { |
1443 | + origin[name] = value; |
1444 | + } |
1445 | + }); |
1446 | + }); |
1447 | + |
1448 | + return proxy; |
1449 | +} |
1450 | + |
1451 | +Pebble = new (function() { |
1452 | + _jskit.make_proxies(this, _jskit.pebble, |
1453 | + ['sendAppMessage', 'showSimpleNotificationOnPebble', 'getAccountToken', 'getWatchToken', |
1454 | + 'addEventListener', 'removeEventListener', 'openURL', 'getTimelineToken', 'timelineSubscribe', |
1455 | + 'timelineUnsubscribe', 'timelineSubscriptions', 'getActiveWatchInfo'] |
1456 | + ); |
1457 | +})(); |
1458 | + |
1459 | +performance = new (function() { |
1460 | + _jskit.make_proxies(this, _jskit.performance, ['now']); |
1461 | +})(); |
1462 | + |
1463 | +function XMLHttpRequest() { |
1464 | + var xhr = _jskit.pebble.createXMLHttpRequest(); |
1465 | + _jskit.make_proxies(this, xhr, |
1466 | + ['open', 'setRequestHeader', 'overrideMimeType', 'send', 'getResponseHeader', |
1467 | + 'getAllResponseHeaders', 'abort', 'addEventListener', 'removeEventListener']); |
1468 | + _jskit.make_properties(this, xhr, |
1469 | + ['readyState', 'response', 'responseText', 'responseType', 'status', |
1470 | + 'statusText', 'timeout', 'onreadystatechange', 'ontimeout', 'onload', |
1471 | + 'onloadstart', 'onloadend', 'onprogress', 'onerror', 'onabort']); |
1472 | + |
1473 | + this.UNSENT = 0; |
1474 | + this.OPENED = 1; |
1475 | + this.HEADERS_RECEIVED = 2; |
1476 | + this.LOADING = 3; |
1477 | + this.DONE = 4; |
1478 | +} |
1479 | + |
1480 | +function setInterval(func, time) { |
1481 | + return _jskit.timer.setInterval(func, time); |
1482 | +} |
1483 | + |
1484 | +function clearInterval(id) { |
1485 | + _jskit.timer.clearInterval(id); |
1486 | +} |
1487 | + |
1488 | +function setTimeout(func, time) { |
1489 | + return _jskit.timer.setTimeout(func, time); |
1490 | +} |
1491 | + |
1492 | +function clearTimeout(id) { |
1493 | + _jskit.timer.clearTimeout(id); |
1494 | +} |
1495 | + |
1496 | +navigator.geolocation = new (function() { |
1497 | + _jskit.make_proxies(this, _jskit.geolocation, |
1498 | + ['getCurrentPosition', 'watchPosition', 'clearWatch'] |
1499 | + ); |
1500 | +})(); |
1501 | + |
1502 | +console = new (function() { |
1503 | + _jskit.make_proxies(this, _jskit.console, |
1504 | + ['log', 'warn', 'error', 'info'] |
1505 | + ); |
1506 | +})(); |
1507 | + |
1508 | +/*localStorage = new (function() { |
1509 | + _jskit.make_proxies(this, _jskit.localstorage, |
1510 | + ['clear', 'getItem', 'setItem', 'removeItem', 'key'] |
1511 | + ); |
1512 | + |
1513 | + _jskit.make_properties(this, _jskit.localstorage, |
1514 | + ['length'] |
1515 | + ); |
1516 | +})();*/ |
1517 | + |
1518 | +//It appears that Proxy is not available since Qt is using Javascript v5 |
1519 | +/*(function() { |
1520 | + var proxy = _jskit.make_proxies({}, _jskit.localstorage, ['set', 'has', 'deleteProperty', 'keys', 'enumerate']); |
1521 | + var methods = _jskit.make_proxies({}, _jskit.localstorage, ['clear', 'getItem', 'setItem', 'removeItem', 'key']); |
1522 | + proxy.get = function get(p, name) { return methods[name] || _jskit.localstorage.get(p, name); } |
1523 | + this.localStorage = Proxy.create(proxy); |
1524 | +})();*/ |
1525 | + |
1526 | +//inspired by https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage |
1527 | +Object.defineProperty(window, "localStorage", new (function () { |
1528 | + var storage = {}; |
1529 | + Object.defineProperty(storage, "getItem", { |
1530 | + value: function (key) { |
1531 | + return (key && storage[key]) ? storage[key] : null; |
1532 | + }, |
1533 | + writable: false, |
1534 | + configurable: false, |
1535 | + enumerable: false |
1536 | + }); |
1537 | + |
1538 | + Object.defineProperty(storage, "key", { |
1539 | + value: function (index) { |
1540 | + return Object.keys(storage)[index]; |
1541 | + }, |
1542 | + writable: false, |
1543 | + configurable: false, |
1544 | + enumerable: false |
1545 | + }); |
1546 | + |
1547 | + Object.defineProperty(storage, "setItem", { |
1548 | + value: function (key, value) { |
1549 | + if (key) { |
1550 | + _jskit.localstorage.setItem(key, value); |
1551 | + storage[key] = (value && value.toString) ? value.toString() : value; |
1552 | + return true; |
1553 | + } |
1554 | + else { |
1555 | + return false; |
1556 | + } |
1557 | + }, |
1558 | + writable: false, |
1559 | + configurable: false, |
1560 | + enumerable: false |
1561 | + }); |
1562 | + |
1563 | + Object.defineProperty(storage, "length", { |
1564 | + get: function () { |
1565 | + return Object.keys(storage).length; |
1566 | + }, |
1567 | + configurable: false, |
1568 | + enumerable: false |
1569 | + }); |
1570 | + |
1571 | + Object.defineProperty(storage, "removeItem", { |
1572 | + value: function (key) { |
1573 | + if (key && storage[key]) { |
1574 | + _jskit.localstorage.removeItem(key); |
1575 | + delete storage[key]; |
1576 | + |
1577 | + return true; |
1578 | + } |
1579 | + else { |
1580 | + return false; |
1581 | + } |
1582 | + }, |
1583 | + writable: false, |
1584 | + configurable: false, |
1585 | + enumerable: false |
1586 | + }); |
1587 | + |
1588 | + Object.defineProperty(storage, "clear", { |
1589 | + value: function (key) { |
1590 | + for (var key in storage) { |
1591 | + storage.removeItem(key); |
1592 | + } |
1593 | + |
1594 | + return true; |
1595 | + }, |
1596 | + writable: false, |
1597 | + configurable: false, |
1598 | + enumerable: false |
1599 | + }); |
1600 | + |
1601 | + this.get = function () { |
1602 | + return storage; |
1603 | + }; |
1604 | + |
1605 | + this.configurable = false; |
1606 | + this.enumerable = true; |
1607 | +})()); |
1608 | + |
1609 | +(function() { |
1610 | + var keys = _jskit.localstorage.keys(); |
1611 | + for (var index in keys) { |
1612 | + var value = _jskit.localstorage.getItem(keys[index]); |
1613 | + localStorage.setItem(keys[index], value); |
1614 | + } |
1615 | +})(); |
1616 | |
1617 | === added file 'rockworkd/libpebble/jskit/jskittimer.cpp' |
1618 | --- rockworkd/libpebble/jskit/jskittimer.cpp 1970-01-01 00:00:00 +0000 |
1619 | +++ rockworkd/libpebble/jskit/jskittimer.cpp 2016-02-06 14:32:32 +0000 |
1620 | @@ -0,0 +1,77 @@ |
1621 | +#include <QTimerEvent> |
1622 | + |
1623 | +#include "jskittimer.h" |
1624 | + |
1625 | +JSKitTimer::JSKitTimer(QJSEngine *engine) : |
1626 | + QObject(engine), |
1627 | + l(metaObject()->className()), |
1628 | + m_engine(engine) |
1629 | +{ |
1630 | +} |
1631 | + |
1632 | +int JSKitTimer::setInterval(QJSValue expression, int delay) //TODO support optional parameters |
1633 | +{ |
1634 | + qCDebug(l) << "Setting interval for " << delay << "ms: " << expression.toString(); |
1635 | + |
1636 | + if (expression.isString() || expression.isCallable()) { |
1637 | + int timerId = startTimer(delay); |
1638 | + m_intervals.insert(timerId, expression); |
1639 | + |
1640 | + return timerId; |
1641 | + } |
1642 | + |
1643 | + return -1; |
1644 | +} |
1645 | + |
1646 | +void JSKitTimer::clearInterval(int timerId) |
1647 | +{ |
1648 | + qCDebug(l) << "Killing interval " << timerId ; |
1649 | + killTimer(timerId); |
1650 | + m_intervals.remove(timerId); |
1651 | +} |
1652 | + |
1653 | +int JSKitTimer::setTimeout(QJSValue expression, int delay) //TODO support optional parameters |
1654 | +{ |
1655 | + qCDebug(l) << "Setting timeout for " << delay << "ms: " << expression.toString(); |
1656 | + |
1657 | + if (expression.isString() || expression.isCallable()) { |
1658 | + int timerId = startTimer(delay); |
1659 | + m_timeouts.insert(timerId, expression); |
1660 | + |
1661 | + return timerId; |
1662 | + } |
1663 | + |
1664 | + return -1; |
1665 | +} |
1666 | + |
1667 | +void JSKitTimer::clearTimeout(int timerId) |
1668 | +{ |
1669 | + qCDebug(l) << "Killing timeout " << timerId ; |
1670 | + killTimer(timerId); |
1671 | + m_timeouts.remove(timerId); |
1672 | +} |
1673 | + |
1674 | +void JSKitTimer::timerEvent(QTimerEvent *event) |
1675 | +{ |
1676 | + int id = event->timerId(); |
1677 | + |
1678 | + QJSValue expression; // find in either intervals or timeouts |
1679 | + if (m_intervals.contains(id)) { |
1680 | + expression = m_intervals.value(id); |
1681 | + } else if (m_timeouts.contains(id)) { |
1682 | + expression = m_timeouts.value(id); |
1683 | + killTimer(id); // timeouts don't repeat |
1684 | + } else { |
1685 | + qCWarning(l) << "Unknown timer event"; |
1686 | + killTimer(id); // interval nor timeout exist. kill the timer |
1687 | + |
1688 | + return; |
1689 | + } |
1690 | + |
1691 | + if (expression.isCallable()) { // call it if it's a function |
1692 | + expression.call().toString(); |
1693 | + } |
1694 | + else { // otherwise evaluate it |
1695 | + m_engine->evaluate(expression.toString()); |
1696 | + } |
1697 | +} |
1698 | |
1699 | === added file 'rockworkd/libpebble/jskit/jskittimer.h' |
1700 | --- rockworkd/libpebble/jskit/jskittimer.h 1970-01-01 00:00:00 +0000 |
1701 | +++ rockworkd/libpebble/jskit/jskittimer.h 2016-02-06 14:32:32 +0000 |
1702 | @@ -0,0 +1,31 @@ |
1703 | +#ifndef JSKITTIMER_P_H |
1704 | +#define JSKITTIMER_P_H |
1705 | + |
1706 | +#include <QLoggingCategory> |
1707 | +#include <QJSValue> |
1708 | +#include <QJSEngine> |
1709 | + |
1710 | +class JSKitTimer : public QObject |
1711 | +{ |
1712 | + Q_OBJECT |
1713 | + QLoggingCategory l; |
1714 | + |
1715 | +public: |
1716 | + explicit JSKitTimer(QJSEngine *engine); |
1717 | + |
1718 | + Q_INVOKABLE int setInterval(QJSValue expression, int delay); |
1719 | + Q_INVOKABLE void clearInterval(int timerId); |
1720 | + |
1721 | + Q_INVOKABLE int setTimeout(QJSValue expression, int delay); |
1722 | + Q_INVOKABLE void clearTimeout(int timerId); |
1723 | + |
1724 | +protected: |
1725 | + void timerEvent(QTimerEvent *event); |
1726 | + |
1727 | +private: |
1728 | + QJSEngine *m_engine; |
1729 | + QHash<int, QJSValue> m_intervals; |
1730 | + QHash<int, QJSValue> m_timeouts; |
1731 | +}; |
1732 | + |
1733 | +#endif // JSKITTIMER_P_H |
1734 | |
1735 | === added file 'rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp' |
1736 | --- rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp 1970-01-01 00:00:00 +0000 |
1737 | +++ rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp 2016-02-06 14:32:32 +0000 |
1738 | @@ -0,0 +1,288 @@ |
1739 | +#include <QBuffer> |
1740 | +#include <QAuthenticator> |
1741 | + |
1742 | +#include "jskitxmlhttprequest.h" |
1743 | +#include "jskitmanager.h" |
1744 | + |
1745 | +JSKitXMLHttpRequest::JSKitXMLHttpRequest(QJSEngine *engine) : |
1746 | + QObject(engine), |
1747 | + l(metaObject()->className()), |
1748 | + m_engine(engine), |
1749 | + m_net(new QNetworkAccessManager(this)), |
1750 | + m_timeout(0), |
1751 | + m_reply(0) |
1752 | +{ |
1753 | + connect(m_net, &QNetworkAccessManager::authenticationRequired, |
1754 | + this, &JSKitXMLHttpRequest::handleAuthenticationRequired); |
1755 | +} |
1756 | + |
1757 | +void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool async, const QString &username, const QString &password) |
1758 | +{ |
1759 | + if (m_reply) { |
1760 | + m_reply->deleteLater(); |
1761 | + m_reply = 0; |
1762 | + } |
1763 | + |
1764 | + m_username = username; |
1765 | + m_password = password; |
1766 | + m_request = QNetworkRequest(QUrl(url)); |
1767 | + m_verb = method; |
1768 | + Q_UNUSED(async); |
1769 | + |
1770 | + qCDebug(l) << "opened to URL" << m_request.url().toString(); |
1771 | +} |
1772 | + |
1773 | +void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString &value) |
1774 | +{ |
1775 | + qCDebug(l) << "setRequestHeader" << header << value; |
1776 | + m_request.setRawHeader(header.toLatin1(), value.toLatin1()); |
1777 | +} |
1778 | + |
1779 | +void JSKitXMLHttpRequest::send(const QJSValue &data) |
1780 | +{ |
1781 | + QByteArray byteData; |
1782 | + |
1783 | + if (data.isUndefined() || data.isNull()) { |
1784 | + // Do nothing, byteData is empty. |
1785 | + } else if (data.isString()) { |
1786 | + byteData = data.toString().toUtf8(); |
1787 | + } else if (data.isObject()) { |
1788 | + if (data.hasProperty("byteLength")) { |
1789 | + // Looks like an ArrayView or an ArrayBufferView! |
1790 | + QJSValue buffer = data.property("buffer"); |
1791 | + if (buffer.isUndefined()) { |
1792 | + // We must assume we've been passed an ArrayBuffer directly |
1793 | + buffer = data; |
1794 | + } |
1795 | + |
1796 | + QJSValue array = data.property("_bytes"); |
1797 | + int byteLength = data.property("byteLength").toInt(); |
1798 | + |
1799 | + if (array.isArray()) { |
1800 | + byteData.reserve(byteLength); |
1801 | + |
1802 | + for (int i = 0; i < byteLength; i++) { |
1803 | + byteData.append(array.property(i).toInt()); |
1804 | + } |
1805 | + |
1806 | + qCDebug(l) << "passed an ArrayBufferView of" << byteData.length() << "bytes"; |
1807 | + } else { |
1808 | + qCWarning(l) << "passed an unknown/invalid ArrayBuffer" << data.toString(); |
1809 | + } |
1810 | + } else { |
1811 | + qCWarning(l) << "passed an unknown object" << data.toString(); |
1812 | + } |
1813 | + |
1814 | + } |
1815 | + |
1816 | + QBuffer *buffer; |
1817 | + if (!byteData.isEmpty()) { |
1818 | + buffer = new QBuffer; |
1819 | + buffer->setData(byteData); |
1820 | + } else { |
1821 | + buffer = 0; |
1822 | + } |
1823 | + |
1824 | + qCDebug(l) << "sending" << m_verb << "to" << m_request.url() << "with" << QString::fromUtf8(byteData); |
1825 | + m_reply = m_net->sendCustomRequest(m_request, m_verb.toLatin1(), buffer); |
1826 | + |
1827 | + connect(m_reply, &QNetworkReply::finished, |
1828 | + this, &JSKitXMLHttpRequest::handleReplyFinished); |
1829 | + connect(m_reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), |
1830 | + this, &JSKitXMLHttpRequest::handleReplyError); |
1831 | + |
1832 | + if (buffer) { |
1833 | + // So that it gets deleted alongside the reply object. |
1834 | + buffer->setParent(m_reply); |
1835 | + } |
1836 | +} |
1837 | + |
1838 | +void JSKitXMLHttpRequest::abort() |
1839 | +{ |
1840 | + if (m_reply) { |
1841 | + m_reply->deleteLater(); |
1842 | + m_reply = 0; |
1843 | + } |
1844 | +} |
1845 | + |
1846 | +QJSValue JSKitXMLHttpRequest::onload() const |
1847 | +{ |
1848 | + return m_onload; |
1849 | +} |
1850 | + |
1851 | +void JSKitXMLHttpRequest::setOnload(const QJSValue &value) |
1852 | +{ |
1853 | + m_onload = value; |
1854 | +} |
1855 | + |
1856 | +QJSValue JSKitXMLHttpRequest::ontimeout() const |
1857 | +{ |
1858 | + return m_ontimeout; |
1859 | +} |
1860 | + |
1861 | +void JSKitXMLHttpRequest::setOntimeout(const QJSValue &value) |
1862 | +{ |
1863 | + m_ontimeout = value; |
1864 | +} |
1865 | + |
1866 | +QJSValue JSKitXMLHttpRequest::onerror() const |
1867 | +{ |
1868 | + return m_onerror; |
1869 | +} |
1870 | + |
1871 | +void JSKitXMLHttpRequest::setOnerror(const QJSValue &value) |
1872 | +{ |
1873 | + m_onerror = value; |
1874 | +} |
1875 | + |
1876 | +uint JSKitXMLHttpRequest::readyState() const |
1877 | +{ |
1878 | + if (!m_reply) { |
1879 | + return UNSENT; |
1880 | + } else if (m_reply->isFinished()) { |
1881 | + return DONE; |
1882 | + } else { |
1883 | + return LOADING; |
1884 | + } |
1885 | +} |
1886 | + |
1887 | +uint JSKitXMLHttpRequest::timeout() const |
1888 | +{ |
1889 | + return m_timeout; |
1890 | +} |
1891 | + |
1892 | +void JSKitXMLHttpRequest::setTimeout(uint value) |
1893 | +{ |
1894 | + m_timeout = value; |
1895 | + // TODO Handle fetch in-progress. |
1896 | +} |
1897 | + |
1898 | +uint JSKitXMLHttpRequest::status() const |
1899 | +{ |
1900 | + if (!m_reply || !m_reply->isFinished()) { |
1901 | + return 0; |
1902 | + } else { |
1903 | + return m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt(); |
1904 | + } |
1905 | +} |
1906 | + |
1907 | +QString JSKitXMLHttpRequest::statusText() const |
1908 | +{ |
1909 | + if (!m_reply || !m_reply->isFinished()) { |
1910 | + return QString(); |
1911 | + } else { |
1912 | + return m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); |
1913 | + } |
1914 | +} |
1915 | + |
1916 | +QString JSKitXMLHttpRequest::responseType() const |
1917 | +{ |
1918 | + return m_responseType; |
1919 | +} |
1920 | + |
1921 | +void JSKitXMLHttpRequest::setResponseType(const QString &type) |
1922 | +{ |
1923 | + qCDebug(l) << "response type set to" << type; |
1924 | + m_responseType = type; |
1925 | +} |
1926 | + |
1927 | +QJSValue JSKitXMLHttpRequest::response() const |
1928 | +{ |
1929 | + if (m_responseType.isEmpty() || m_responseType == "text") { |
1930 | + return m_engine->toScriptValue(QString::fromUtf8(m_response)); |
1931 | + } else if (m_responseType == "arraybuffer") { |
1932 | + QJSValue arrayBufferProto = m_engine->globalObject().property("ArrayBuffer").property("prototype"); |
1933 | + QJSValue arrayBuf = m_engine->newObject(); |
1934 | + |
1935 | + if (!arrayBufferProto.isUndefined()) { |
1936 | + arrayBuf.setPrototype(arrayBufferProto); |
1937 | + arrayBuf.setProperty("byteLength", m_engine->toScriptValue<uint>(m_response.size())); |
1938 | + |
1939 | + QJSValue array = m_engine->newArray(m_response.size()); |
1940 | + for (int i = 0; i < m_response.size(); i++) { |
1941 | + array.setProperty(i, m_engine->toScriptValue<int>(m_response[i])); |
1942 | + } |
1943 | + |
1944 | + arrayBuf.setProperty("_bytes", array); |
1945 | + qCDebug(l) << "returning ArrayBuffer of" << m_response.size() << "bytes"; |
1946 | + } else { |
1947 | + qCWarning(l) << "Cannot find proto of ArrayBuffer"; |
1948 | + } |
1949 | + |
1950 | + return arrayBuf; |
1951 | + } else { |
1952 | + qCWarning(l) << "unsupported responseType:" << m_responseType; |
1953 | + return m_engine->toScriptValue<void*>(0); |
1954 | + } |
1955 | +} |
1956 | + |
1957 | +QString JSKitXMLHttpRequest::responseText() const |
1958 | +{ |
1959 | + return QString::fromUtf8(m_response); |
1960 | +} |
1961 | + |
1962 | +void JSKitXMLHttpRequest::handleReplyFinished() |
1963 | +{ |
1964 | + if (!m_reply) { |
1965 | + qCDebug(l) << "reply finished too late"; |
1966 | + return; |
1967 | + } |
1968 | + |
1969 | + m_response = m_reply->readAll(); |
1970 | + qCDebug(l) << "reply finished, reply text:" << QString::fromUtf8(m_response); |
1971 | + |
1972 | + emit readyStateChanged(); |
1973 | + emit statusChanged(); |
1974 | + emit statusTextChanged(); |
1975 | + emit responseChanged(); |
1976 | + emit responseTextChanged(); |
1977 | + |
1978 | + if (m_onload.isCallable()) { |
1979 | + qCDebug(l) << "going to call onload handler:" << m_onload.toString(); |
1980 | + |
1981 | + QJSValue result = m_onload.callWithInstance(m_engine->newQObject(this)); |
1982 | + if (result.isError()) { |
1983 | + qCWarning(l) << "JS error on onload handler:" << JSKitManager::describeError(result); |
1984 | + } |
1985 | + } else { |
1986 | + qCDebug(l) << "No onload set"; |
1987 | + } |
1988 | +} |
1989 | + |
1990 | +void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) |
1991 | +{ |
1992 | + if (!m_reply) { |
1993 | + qCDebug(l) << "reply error too late"; |
1994 | + return; |
1995 | + } |
1996 | + |
1997 | + qCDebug(l) << "reply error" << code; |
1998 | + |
1999 | + emit readyStateChanged(); |
2000 | + emit statusChanged(); |
2001 | + emit statusTextChanged(); |
2002 | + |
2003 | + if (m_onerror.isCallable()) { |
2004 | + qCDebug(l) << "going to call onerror handler:" << m_onload.toString(); |
2005 | + QJSValue result = m_onerror.callWithInstance(m_engine->newQObject(this)); |
2006 | + if (result.isError()) { |
2007 | + qCWarning(l) << "JS error on onerror handler:" << JSKitManager::describeError(result); |
2008 | + } |
2009 | + } |
2010 | +} |
2011 | + |
2012 | +void JSKitXMLHttpRequest::handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth) |
2013 | +{ |
2014 | + if (m_reply == reply) { |
2015 | + qCDebug(l) << "authentication required"; |
2016 | + |
2017 | + if (!m_username.isEmpty() || !m_password.isEmpty()) { |
2018 | + qCDebug(l) << "using provided authorization:" << m_username; |
2019 | + |
2020 | + auth->setUser(m_username); |
2021 | + auth->setPassword(m_password); |
2022 | + } else { |
2023 | + qCDebug(l) << "no username or password provided"; |
2024 | + } |
2025 | + } |
2026 | +} |
2027 | |
2028 | === added file 'rockworkd/libpebble/jskit/jskitxmlhttprequest.h' |
2029 | --- rockworkd/libpebble/jskit/jskitxmlhttprequest.h 1970-01-01 00:00:00 +0000 |
2030 | +++ rockworkd/libpebble/jskit/jskitxmlhttprequest.h 2016-02-06 14:32:32 +0000 |
2031 | @@ -0,0 +1,91 @@ |
2032 | +#ifndef JSKITXMLHTTPREQUEST_P_H |
2033 | +#define JSKITXMLHTTPREQUEST_P_H |
2034 | + |
2035 | +#include <QNetworkRequest> |
2036 | +#include <QNetworkReply> |
2037 | +#include <QJSEngine> |
2038 | +#include <QLoggingCategory> |
2039 | + |
2040 | +class JSKitXMLHttpRequest : public QObject |
2041 | +{ |
2042 | + Q_OBJECT |
2043 | + QLoggingCategory l; |
2044 | + |
2045 | + Q_PROPERTY(QJSValue onload READ onload WRITE setOnload) |
2046 | + Q_PROPERTY(QJSValue ontimeout READ ontimeout WRITE setOntimeout) |
2047 | + Q_PROPERTY(QJSValue onerror READ onerror WRITE setOnerror) |
2048 | + Q_PROPERTY(uint readyState READ readyState NOTIFY readyStateChanged) |
2049 | + Q_PROPERTY(uint timeout READ timeout WRITE setTimeout) |
2050 | + Q_PROPERTY(uint status READ status NOTIFY statusChanged) |
2051 | + Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged) |
2052 | + Q_PROPERTY(QString responseType READ responseType WRITE setResponseType) |
2053 | + Q_PROPERTY(QJSValue response READ response NOTIFY responseChanged) |
2054 | + Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged) |
2055 | + |
2056 | +public: |
2057 | + explicit JSKitXMLHttpRequest(QJSEngine *engine); |
2058 | + |
2059 | + enum ReadyStates { |
2060 | + UNSENT = 0, |
2061 | + OPENED = 1, |
2062 | + HEADERS_RECEIVED = 2, |
2063 | + LOADING = 3, |
2064 | + DONE = 4 |
2065 | + }; |
2066 | + Q_ENUMS(ReadyStates) |
2067 | + |
2068 | + Q_INVOKABLE void open(const QString &method, const QString &url, bool async = false, const QString &username = QString(), const QString &password = QString()); |
2069 | + Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value); |
2070 | + Q_INVOKABLE void send(const QJSValue &data = QJSValue(QJSValue::NullValue)); |
2071 | + Q_INVOKABLE void abort(); |
2072 | + |
2073 | + QJSValue onload() const; |
2074 | + void setOnload(const QJSValue &value); |
2075 | + QJSValue ontimeout() const; |
2076 | + void setOntimeout(const QJSValue &value); |
2077 | + QJSValue onerror() const; |
2078 | + void setOnerror(const QJSValue &value); |
2079 | + |
2080 | + uint readyState() const; |
2081 | + |
2082 | + uint timeout() const; |
2083 | + void setTimeout(uint value); |
2084 | + |
2085 | + uint status() const; |
2086 | + QString statusText() const; |
2087 | + |
2088 | + QString responseType() const; |
2089 | + void setResponseType(const QString& type); |
2090 | + |
2091 | + QJSValue response() const; |
2092 | + QString responseText() const; |
2093 | + |
2094 | +signals: |
2095 | + void readyStateChanged(); |
2096 | + void statusChanged(); |
2097 | + void statusTextChanged(); |
2098 | + void responseChanged(); |
2099 | + void responseTextChanged(); |
2100 | + |
2101 | +private slots: |
2102 | + void handleReplyFinished(); |
2103 | + void handleReplyError(QNetworkReply::NetworkError code); |
2104 | + void handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth); |
2105 | + |
2106 | +private: |
2107 | + QJSEngine *m_engine; |
2108 | + QNetworkAccessManager *m_net; |
2109 | + QString m_verb; |
2110 | + uint m_timeout; |
2111 | + QString m_username; |
2112 | + QString m_password; |
2113 | + QNetworkRequest m_request; |
2114 | + QNetworkReply *m_reply; |
2115 | + QString m_responseType; |
2116 | + QByteArray m_response; |
2117 | + QJSValue m_onload; |
2118 | + QJSValue m_ontimeout; |
2119 | + QJSValue m_onerror; |
2120 | +}; |
2121 | + |
2122 | +#endif // JSKITXMLHTTPREQUEST_P_H |
2123 | |
2124 | === added file 'rockworkd/libpebble/jskit/typedarray.js' |
2125 | --- rockworkd/libpebble/jskit/typedarray.js 1970-01-01 00:00:00 +0000 |
2126 | +++ rockworkd/libpebble/jskit/typedarray.js 2016-02-06 14:32:32 +0000 |
2127 | @@ -0,0 +1,1030 @@ |
2128 | +/* |
2129 | + Copyright (c) 2010, Linden Research, Inc. |
2130 | + Copyright (c) 2014, Joshua Bell |
2131 | + |
2132 | + Permission is hereby granted, free of charge, to any person obtaining a copy |
2133 | + of this software and associated documentation files (the "Software"), to deal |
2134 | + in the Software without restriction, including without limitation the rights |
2135 | + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
2136 | + copies of the Software, and to permit persons to whom the Software is |
2137 | + furnished to do so, subject to the following conditions: |
2138 | + |
2139 | + The above copyright notice and this permission notice shall be included in |
2140 | + all copies or substantial portions of the Software. |
2141 | + |
2142 | + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
2143 | + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
2144 | + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
2145 | + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
2146 | + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
2147 | + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
2148 | + THE SOFTWARE. |
2149 | + $/LicenseInfo$ |
2150 | + */ |
2151 | + |
2152 | +// Original can be found at: |
2153 | +// https://bitbucket.org/lindenlab/llsd |
2154 | +// Modifications by Joshua Bell inexorabletash@gmail.com |
2155 | +// https://github.com/inexorabletash/polyfill |
2156 | + |
2157 | +// ES3/ES5 implementation of the Krhonos Typed Array Specification |
2158 | +// Ref: http://www.khronos.org/registry/typedarray/specs/latest/ |
2159 | +// Date: 2011-02-01 |
2160 | +// |
2161 | +// Variations: |
2162 | +// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) |
2163 | +// * Gradually migrating structure from Khronos spec to ES6 spec |
2164 | +(function(global) { |
2165 | + 'use strict'; |
2166 | + var undefined = (void 0); // Paranoia |
2167 | + |
2168 | + // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to |
2169 | + // create, and consume so much memory, that the browser appears frozen. |
2170 | + var MAX_ARRAY_LENGTH = 1e5; |
2171 | + |
2172 | + // Approximations of internal ECMAScript conversion functions |
2173 | + function Type(v) { |
2174 | + switch(typeof v) { |
2175 | + case 'undefined': return 'undefined'; |
2176 | + case 'boolean': return 'boolean'; |
2177 | + case 'number': return 'number'; |
2178 | + case 'string': return 'string'; |
2179 | + default: return v === null ? 'null' : 'object'; |
2180 | + } |
2181 | + } |
2182 | + |
2183 | + // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues: |
2184 | + function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); } |
2185 | + function IsCallable(o) { return typeof o === 'function'; } |
2186 | + function ToObject(v) { |
2187 | + if (v === null || v === undefined) throw TypeError(); |
2188 | + return Object(v); |
2189 | + } |
2190 | + function ToInt32(v) { return v >> 0; } |
2191 | + function ToUint32(v) { return v >>> 0; } |
2192 | + |
2193 | + // Snapshot intrinsics |
2194 | + var LN2 = Math.LN2, |
2195 | + abs = Math.abs, |
2196 | + floor = Math.floor, |
2197 | + log = Math.log, |
2198 | + max = Math.max, |
2199 | + min = Math.min, |
2200 | + pow = Math.pow, |
2201 | + round = Math.round; |
2202 | + |
2203 | + // emulate ES5 getter/setter API using legacy APIs |
2204 | + // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx |
2205 | + // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but |
2206 | + // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless) |
2207 | + |
2208 | + (function() { |
2209 | + var orig = Object.defineProperty; |
2210 | + var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}()); |
2211 | + |
2212 | + if (!orig || dom_only) { |
2213 | + Object.defineProperty = function (o, prop, desc) { |
2214 | + // In IE8 try built-in implementation for defining properties on DOM prototypes. |
2215 | + if (orig) |
2216 | + try { return orig(o, prop, desc); } catch (_) {} |
2217 | + if (o !== Object(o)) |
2218 | + throw TypeError('Object.defineProperty called on non-object'); |
2219 | + if (Object.prototype.__defineGetter__ && ('get' in desc)) |
2220 | + Object.prototype.__defineGetter__.call(o, prop, desc.get); |
2221 | + if (Object.prototype.__defineSetter__ && ('set' in desc)) |
2222 | + Object.prototype.__defineSetter__.call(o, prop, desc.set); |
2223 | + if ('value' in desc) |
2224 | + o[prop] = desc.value; |
2225 | + return o; |
2226 | + }; |
2227 | + } |
2228 | + }()); |
2229 | + |
2230 | + // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value) |
2231 | + // for index in 0 ... obj.length |
2232 | + function makeArrayAccessors(obj) { |
2233 | + if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill'); |
2234 | + |
2235 | + function makeArrayAccessor(index) { |
2236 | + Object.defineProperty(obj, index, { |
2237 | + 'get': function() { return obj._getter(index); }, |
2238 | + 'set': function(v) { obj._setter(index, v); }, |
2239 | + enumerable: true, |
2240 | + configurable: false |
2241 | + }); |
2242 | + } |
2243 | + |
2244 | + var i; |
2245 | + for (i = 0; i < obj.length; i += 1) { |
2246 | + makeArrayAccessor(i); |
2247 | + } |
2248 | + } |
2249 | + |
2250 | + // Internal conversion functions: |
2251 | + // pack<Type>() - take a number (interpreted as Type), output a byte array |
2252 | + // unpack<Type>() - take a byte array, output a Type-like number |
2253 | + |
2254 | + function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; } |
2255 | + function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; } |
2256 | + |
2257 | + function packI8(n) { return [n & 0xff]; } |
2258 | + function unpackI8(bytes) { return as_signed(bytes[0], 8); } |
2259 | + |
2260 | + function packU8(n) { return [n & 0xff]; } |
2261 | + function unpackU8(bytes) { return as_unsigned(bytes[0], 8); } |
2262 | + |
2263 | + function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; } |
2264 | + |
2265 | + function packI16(n) { return [(n >> 8) & 0xff, n & 0xff]; } |
2266 | + function unpackI16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); } |
2267 | + |
2268 | + function packU16(n) { return [(n >> 8) & 0xff, n & 0xff]; } |
2269 | + function unpackU16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); } |
2270 | + |
2271 | + function packI32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } |
2272 | + function unpackI32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } |
2273 | + |
2274 | + function packU32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } |
2275 | + function unpackU32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } |
2276 | + |
2277 | + function packIEEE754(v, ebits, fbits) { |
2278 | + |
2279 | + var bias = (1 << (ebits - 1)) - 1, |
2280 | + s, e, f, ln, |
2281 | + i, bits, str, bytes; |
2282 | + |
2283 | + function roundToEven(n) { |
2284 | + var w = floor(n), f = n - w; |
2285 | + if (f < 0.5) |
2286 | + return w; |
2287 | + if (f > 0.5) |
2288 | + return w + 1; |
2289 | + return w % 2 ? w + 1 : w; |
2290 | + } |
2291 | + |
2292 | + // Compute sign, exponent, fraction |
2293 | + if (v !== v) { |
2294 | + // NaN |
2295 | + // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping |
2296 | + e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0; |
2297 | + } else if (v === Infinity || v === -Infinity) { |
2298 | + e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0; |
2299 | + } else if (v === 0) { |
2300 | + e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0; |
2301 | + } else { |
2302 | + s = v < 0; |
2303 | + v = abs(v); |
2304 | + |
2305 | + if (v >= pow(2, 1 - bias)) { |
2306 | + e = min(floor(log(v) / LN2), 1023); |
2307 | + f = roundToEven(v / pow(2, e) * pow(2, fbits)); |
2308 | + if (f / pow(2, fbits) >= 2) { |
2309 | + e = e + 1; |
2310 | + f = 1; |
2311 | + } |
2312 | + if (e > bias) { |
2313 | + // Overflow |
2314 | + e = (1 << ebits) - 1; |
2315 | + f = 0; |
2316 | + } else { |
2317 | + // Normalized |
2318 | + e = e + bias; |
2319 | + f = f - pow(2, fbits); |
2320 | + } |
2321 | + } else { |
2322 | + // Denormalized |
2323 | + e = 0; |
2324 | + f = roundToEven(v / pow(2, 1 - bias - fbits)); |
2325 | + } |
2326 | + } |
2327 | + |
2328 | + // Pack sign, exponent, fraction |
2329 | + bits = []; |
2330 | + for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); } |
2331 | + for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); } |
2332 | + bits.push(s ? 1 : 0); |
2333 | + bits.reverse(); |
2334 | + str = bits.join(''); |
2335 | + |
2336 | + // Bits to bytes |
2337 | + bytes = []; |
2338 | + while (str.length) { |
2339 | + bytes.push(parseInt(str.substring(0, 8), 2)); |
2340 | + str = str.substring(8); |
2341 | + } |
2342 | + return bytes; |
2343 | + } |
2344 | + |
2345 | + function unpackIEEE754(bytes, ebits, fbits) { |
2346 | + // Bytes to bits |
2347 | + var bits = [], i, j, b, str, |
2348 | + bias, s, e, f; |
2349 | + |
2350 | + for (i = bytes.length; i; i -= 1) { |
2351 | + b = bytes[i - 1]; |
2352 | + for (j = 8; j; j -= 1) { |
2353 | + bits.push(b % 2 ? 1 : 0); b = b >> 1; |
2354 | + } |
2355 | + } |
2356 | + bits.reverse(); |
2357 | + str = bits.join(''); |
2358 | + |
2359 | + // Unpack sign, exponent, fraction |
2360 | + bias = (1 << (ebits - 1)) - 1; |
2361 | + s = parseInt(str.substring(0, 1), 2) ? -1 : 1; |
2362 | + e = parseInt(str.substring(1, 1 + ebits), 2); |
2363 | + f = parseInt(str.substring(1 + ebits), 2); |
2364 | + |
2365 | + // Produce number |
2366 | + if (e === (1 << ebits) - 1) { |
2367 | + return f !== 0 ? NaN : s * Infinity; |
2368 | + } else if (e > 0) { |
2369 | + // Normalized |
2370 | + return s * pow(2, e - bias) * (1 + f / pow(2, fbits)); |
2371 | + } else if (f !== 0) { |
2372 | + // Denormalized |
2373 | + return s * pow(2, -(bias - 1)) * (f / pow(2, fbits)); |
2374 | + } else { |
2375 | + return s < 0 ? -0 : 0; |
2376 | + } |
2377 | + } |
2378 | + |
2379 | + function unpackF64(b) { return unpackIEEE754(b, 11, 52); } |
2380 | + function packF64(v) { return packIEEE754(v, 11, 52); } |
2381 | + function unpackF32(b) { return unpackIEEE754(b, 8, 23); } |
2382 | + function packF32(v) { return packIEEE754(v, 8, 23); } |
2383 | + |
2384 | + // |
2385 | + // 3 The ArrayBuffer Type |
2386 | + // |
2387 | + |
2388 | + (function() { |
2389 | + |
2390 | + function ArrayBuffer(length) { |
2391 | + length = ToInt32(length); |
2392 | + if (length < 0) throw RangeError('ArrayBuffer size is not a small enough positive integer.'); |
2393 | + Object.defineProperty(this, 'byteLength', {value: length}); |
2394 | + Object.defineProperty(this, '_bytes', {value: Array(length)}); |
2395 | + |
2396 | + for (var i = 0; i < length; i += 1) |
2397 | + this._bytes[i] = 0; |
2398 | + } |
2399 | + |
2400 | + global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer; |
2401 | + |
2402 | + // |
2403 | + // 5 The Typed Array View Types |
2404 | + // |
2405 | + |
2406 | + function $TypedArray$() { |
2407 | + |
2408 | + // %TypedArray% ( length ) |
2409 | + if (!arguments.length || typeof arguments[0] !== 'object') { |
2410 | + return (function(length) { |
2411 | + length = ToInt32(length); |
2412 | + if (length < 0) throw RangeError('length is not a small enough positive integer.'); |
2413 | + Object.defineProperty(this, 'length', {value: length}); |
2414 | + Object.defineProperty(this, 'byteLength', {value: length * this.BYTES_PER_ELEMENT}); |
2415 | + Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(this.byteLength)}); |
2416 | + Object.defineProperty(this, 'byteOffset', {value: 0}); |
2417 | + |
2418 | + }).apply(this, arguments); |
2419 | + } |
2420 | + |
2421 | + // %TypedArray% ( typedArray ) |
2422 | + if (arguments.length >= 1 && |
2423 | + Type(arguments[0]) === 'object' && |
2424 | + arguments[0] instanceof $TypedArray$) { |
2425 | + return (function(typedArray){ |
2426 | + if (this.constructor !== typedArray.constructor) throw TypeError(); |
2427 | + |
2428 | + var byteLength = typedArray.length * this.BYTES_PER_ELEMENT; |
2429 | + Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); |
2430 | + Object.defineProperty(this, 'byteLength', {value: byteLength}); |
2431 | + Object.defineProperty(this, 'byteOffset', {value: 0}); |
2432 | + Object.defineProperty(this, 'length', {value: typedArray.length}); |
2433 | + |
2434 | + for (var i = 0; i < this.length; i += 1) |
2435 | + this._setter(i, typedArray._getter(i)); |
2436 | + |
2437 | + }).apply(this, arguments); |
2438 | + } |
2439 | + |
2440 | + // %TypedArray% ( array ) |
2441 | + if (arguments.length >= 1 && |
2442 | + Type(arguments[0]) === 'object' && |
2443 | + !(arguments[0] instanceof $TypedArray$) && |
2444 | + !(arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { |
2445 | + return (function(array) { |
2446 | + |
2447 | + var byteLength = array.length * this.BYTES_PER_ELEMENT; |
2448 | + Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); |
2449 | + Object.defineProperty(this, 'byteLength', {value: byteLength}); |
2450 | + Object.defineProperty(this, 'byteOffset', {value: 0}); |
2451 | + Object.defineProperty(this, 'length', {value: array.length}); |
2452 | + |
2453 | + for (var i = 0; i < this.length; i += 1) { |
2454 | + var s = array[i]; |
2455 | + this._setter(i, Number(s)); |
2456 | + } |
2457 | + }).apply(this, arguments); |
2458 | + } |
2459 | + |
2460 | + // %TypedArray% ( buffer, byteOffset=0, length=undefined ) |
2461 | + if (arguments.length >= 1 && |
2462 | + Type(arguments[0]) === 'object' && |
2463 | + (arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { |
2464 | + return (function(buffer, byteOffset, length) { |
2465 | + |
2466 | + byteOffset = ToUint32(byteOffset); |
2467 | + if (byteOffset > buffer.byteLength) |
2468 | + throw RangeError('byteOffset out of range'); |
2469 | + |
2470 | + // The given byteOffset must be a multiple of the element |
2471 | + // size of the specific type, otherwise an exception is raised. |
2472 | + if (byteOffset % this.BYTES_PER_ELEMENT) |
2473 | + throw RangeError('buffer length minus the byteOffset is not a multiple of the element size.'); |
2474 | + |
2475 | + if (length === undefined) { |
2476 | + var byteLength = buffer.byteLength - byteOffset; |
2477 | + if (byteLength % this.BYTES_PER_ELEMENT) |
2478 | + throw RangeError('length of buffer minus byteOffset not a multiple of the element size'); |
2479 | + length = byteLength / this.BYTES_PER_ELEMENT; |
2480 | + |
2481 | + } else { |
2482 | + length = ToUint32(length); |
2483 | + byteLength = length * this.BYTES_PER_ELEMENT; |
2484 | + } |
2485 | + |
2486 | + if ((byteOffset + byteLength) > buffer.byteLength) |
2487 | + throw RangeError('byteOffset and length reference an area beyond the end of the buffer'); |
2488 | + |
2489 | + Object.defineProperty(this, 'buffer', {value: buffer}); |
2490 | + Object.defineProperty(this, 'byteLength', {value: byteLength}); |
2491 | + Object.defineProperty(this, 'byteOffset', {value: byteOffset}); |
2492 | + Object.defineProperty(this, 'length', {value: length}); |
2493 | + |
2494 | + }).apply(this, arguments); |
2495 | + } |
2496 | + |
2497 | + // %TypedArray% ( all other argument combinations ) |
2498 | + throw TypeError(); |
2499 | + } |
2500 | + |
2501 | + // Properties of the %TypedArray Instrinsic Object |
2502 | + |
2503 | + // %TypedArray%.from ( source , mapfn=undefined, thisArg=undefined ) |
2504 | + Object.defineProperty($TypedArray$, 'from', {value: function(iterable) { |
2505 | + return new this(iterable); |
2506 | + }}); |
2507 | + |
2508 | + // %TypedArray%.of ( ...items ) |
2509 | + Object.defineProperty($TypedArray$, 'of', {value: function(/*...items*/) { |
2510 | + return new this(arguments); |
2511 | + }}); |
2512 | + |
2513 | + // %TypedArray%.prototype |
2514 | + var $TypedArrayPrototype$ = {}; |
2515 | + $TypedArray$.prototype = $TypedArrayPrototype$; |
2516 | + |
2517 | + // WebIDL: getter type (unsigned long index); |
2518 | + Object.defineProperty($TypedArray$.prototype, '_getter', {value: function(index) { |
2519 | + if (arguments.length < 1) throw SyntaxError('Not enough arguments'); |
2520 | + |
2521 | + index = ToUint32(index); |
2522 | + if (index >= this.length) |
2523 | + return undefined; |
2524 | + |
2525 | + var bytes = [], i, o; |
2526 | + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; |
2527 | + i < this.BYTES_PER_ELEMENT; |
2528 | + i += 1, o += 1) { |
2529 | + bytes.push(this.buffer._bytes[o]); |
2530 | + } |
2531 | + return this._unpack(bytes); |
2532 | + }}); |
2533 | + |
2534 | + // NONSTANDARD: convenience alias for getter: type get(unsigned long index); |
2535 | + Object.defineProperty($TypedArray$.prototype, 'get', {value: $TypedArray$.prototype._getter}); |
2536 | + |
2537 | + // WebIDL: setter void (unsigned long index, type value); |
2538 | + Object.defineProperty($TypedArray$.prototype, '_setter', {value: function(index, value) { |
2539 | + if (arguments.length < 2) throw SyntaxError('Not enough arguments'); |
2540 | + |
2541 | + index = ToUint32(index); |
2542 | + if (index >= this.length) |
2543 | + return; |
2544 | + |
2545 | + var bytes = this._pack(value), i, o; |
2546 | + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; |
2547 | + i < this.BYTES_PER_ELEMENT; |
2548 | + i += 1, o += 1) { |
2549 | + this.buffer._bytes[o] = bytes[i]; |
2550 | + } |
2551 | + }}); |
2552 | + |
2553 | + // get %TypedArray%.prototype.buffer |
2554 | + // get %TypedArray%.prototype.byteLength |
2555 | + // get %TypedArray%.prototype.byteOffset |
2556 | + // -- applied directly to the object in the constructor |
2557 | + |
2558 | + // %TypedArray%.prototype.constructor |
2559 | + Object.defineProperty($TypedArray$.prototype, 'constructor', {value: $TypedArray$}); |
2560 | + |
2561 | + // %TypedArray%.prototype.copyWithin (target, start, end = this.length ) |
2562 | + Object.defineProperty($TypedArray$.prototype, 'copyWithin', {value: function(target, start) { |
2563 | + var end = arguments[2]; |
2564 | + |
2565 | + var o = ToObject(this); |
2566 | + var lenVal = o.length; |
2567 | + var len = ToUint32(lenVal); |
2568 | + len = max(len, 0); |
2569 | + var relativeTarget = ToInt32(target); |
2570 | + var to; |
2571 | + if (relativeTarget < 0) |
2572 | + to = max(len + relativeTarget, 0); |
2573 | + else |
2574 | + to = min(relativeTarget, len); |
2575 | + var relativeStart = ToInt32(start); |
2576 | + var from; |
2577 | + if (relativeStart < 0) |
2578 | + from = max(len + relativeStart, 0); |
2579 | + else |
2580 | + from = min(relativeStart, len); |
2581 | + var relativeEnd; |
2582 | + if (end === undefined) |
2583 | + relativeEnd = len; |
2584 | + else |
2585 | + relativeEnd = ToInt32(end); |
2586 | + var final; |
2587 | + if (relativeEnd < 0) |
2588 | + final = max(len + relativeEnd, 0); |
2589 | + else |
2590 | + final = min(relativeEnd, len); |
2591 | + var count = min(final - from, len - to); |
2592 | + var direction; |
2593 | + if (from < to && to < from + count) { |
2594 | + direction = -1; |
2595 | + from = from + count - 1; |
2596 | + to = to + count - 1; |
2597 | + } else { |
2598 | + direction = 1; |
2599 | + } |
2600 | + while (count > 0) { |
2601 | + o._setter(to, o._getter(from)); |
2602 | + from = from + direction; |
2603 | + to = to + direction; |
2604 | + count = count - 1; |
2605 | + } |
2606 | + return o; |
2607 | + }}); |
2608 | + |
2609 | + // %TypedArray%.prototype.entries ( ) |
2610 | + // -- defined in es6.js to shim browsers w/ native TypedArrays |
2611 | + |
2612 | + // %TypedArray%.prototype.every ( callbackfn, thisArg = undefined ) |
2613 | + Object.defineProperty($TypedArray$.prototype, 'every', {value: function(callbackfn) { |
2614 | + if (this === undefined || this === null) throw TypeError(); |
2615 | + var t = Object(this); |
2616 | + var len = ToUint32(t.length); |
2617 | + if (!IsCallable(callbackfn)) throw TypeError(); |
2618 | + var thisArg = arguments[1]; |
2619 | + for (var i = 0; i < len; i++) { |
2620 | + if (!callbackfn.call(thisArg, t._getter(i), i, t)) |
2621 | + return false; |
2622 | + } |
2623 | + return true; |
2624 | + }}); |
2625 | + |
2626 | + // %TypedArray%.prototype.fill (value, start = 0, end = this.length ) |
2627 | + Object.defineProperty($TypedArray$.prototype, 'fill', {value: function(value) { |
2628 | + var start = arguments[1], |
2629 | + end = arguments[2]; |
2630 | + |
2631 | + var o = ToObject(this); |
2632 | + var lenVal = o.length; |
2633 | + var len = ToUint32(lenVal); |
2634 | + len = max(len, 0); |
2635 | + var relativeStart = ToInt32(start); |
2636 | + var k; |
2637 | + if (relativeStart < 0) |
2638 | + k = max((len + relativeStart), 0); |
2639 | + else |
2640 | + k = min(relativeStart, len); |
2641 | + var relativeEnd; |
2642 | + if (end === undefined) |
2643 | + relativeEnd = len; |
2644 | + else |
2645 | + relativeEnd = ToInt32(end); |
2646 | + var final; |
2647 | + if (relativeEnd < 0) |
2648 | + final = max((len + relativeEnd), 0); |
2649 | + else |
2650 | + final = min(relativeEnd, len); |
2651 | + while (k < final) { |
2652 | + o._setter(k, value); |
2653 | + k += 1; |
2654 | + } |
2655 | + return o; |
2656 | + }}); |
2657 | + |
2658 | + // %TypedArray%.prototype.filter ( callbackfn, thisArg = undefined ) |
2659 | + Object.defineProperty($TypedArray$.prototype, 'filter', {value: function(callbackfn) { |
2660 | + if (this === undefined || this === null) throw TypeError(); |
2661 | + var t = Object(this); |
2662 | + var len = ToUint32(t.length); |
2663 | + if (!IsCallable(callbackfn)) throw TypeError(); |
2664 | + var res = []; |
2665 | + var thisp = arguments[1]; |
2666 | + for (var i = 0; i < len; i++) { |
2667 | + var val = t._getter(i); // in case fun mutates this |
2668 | + if (callbackfn.call(thisp, val, i, t)) |
2669 | + res.push(val); |
2670 | + } |
2671 | + return new this.constructor(res); |
2672 | + }}); |
2673 | + |
2674 | + // %TypedArray%.prototype.find (predicate, thisArg = undefined) |
2675 | + Object.defineProperty($TypedArray$.prototype, 'find', {value: function(predicate) { |
2676 | + var o = ToObject(this); |
2677 | + var lenValue = o.length; |
2678 | + var len = ToUint32(lenValue); |
2679 | + if (!IsCallable(predicate)) throw TypeError(); |
2680 | + var t = arguments.length > 1 ? arguments[1] : undefined; |
2681 | + var k = 0; |
2682 | + while (k < len) { |
2683 | + var kValue = o._getter(k); |
2684 | + var testResult = predicate.call(t, kValue, k, o); |
2685 | + if (Boolean(testResult)) |
2686 | + return kValue; |
2687 | + ++k; |
2688 | + } |
2689 | + return undefined; |
2690 | + }}); |
2691 | + |
2692 | + // %TypedArray%.prototype.findIndex ( predicate, thisArg = undefined ) |
2693 | + Object.defineProperty($TypedArray$.prototype, 'findIndex', {value: function(predicate) { |
2694 | + var o = ToObject(this); |
2695 | + var lenValue = o.length; |
2696 | + var len = ToUint32(lenValue); |
2697 | + if (!IsCallable(predicate)) throw TypeError(); |
2698 | + var t = arguments.length > 1 ? arguments[1] : undefined; |
2699 | + var k = 0; |
2700 | + while (k < len) { |
2701 | + var kValue = o._getter(k); |
2702 | + var testResult = predicate.call(t, kValue, k, o); |
2703 | + if (Boolean(testResult)) |
2704 | + return k; |
2705 | + ++k; |
2706 | + } |
2707 | + return -1; |
2708 | + }}); |
2709 | + |
2710 | + // %TypedArray%.prototype.forEach ( callbackfn, thisArg = undefined ) |
2711 | + Object.defineProperty($TypedArray$.prototype, 'forEach', {value: function(callbackfn) { |
2712 | + if (this === undefined || this === null) throw TypeError(); |
2713 | + var t = Object(this); |
2714 | + var len = ToUint32(t.length); |
2715 | + if (!IsCallable(callbackfn)) throw TypeError(); |
2716 | + var thisp = arguments[1]; |
2717 | + for (var i = 0; i < len; i++) |
2718 | + callbackfn.call(thisp, t._getter(i), i, t); |
2719 | + }}); |
2720 | + |
2721 | + // %TypedArray%.prototype.indexOf (searchElement, fromIndex = 0 ) |
2722 | + Object.defineProperty($TypedArray$.prototype, 'indexOf', {value: function(searchElement) { |
2723 | + if (this === undefined || this === null) throw TypeError(); |
2724 | + var t = Object(this); |
2725 | + var len = ToUint32(t.length); |
2726 | + if (len === 0) return -1; |
2727 | + var n = 0; |
2728 | + if (arguments.length > 0) { |
2729 | + n = Number(arguments[1]); |
2730 | + if (n !== n) { |
2731 | + n = 0; |
2732 | + } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { |
2733 | + n = (n > 0 || -1) * floor(abs(n)); |
2734 | + } |
2735 | + } |
2736 | + if (n >= len) return -1; |
2737 | + var k = n >= 0 ? n : max(len - abs(n), 0); |
2738 | + for (; k < len; k++) { |
2739 | + if (t._getter(k) === searchElement) { |
2740 | + return k; |
2741 | + } |
2742 | + } |
2743 | + return -1; |
2744 | + }}); |
2745 | + |
2746 | + // %TypedArray%.prototype.join ( separator ) |
2747 | + Object.defineProperty($TypedArray$.prototype, 'join', {value: function(separator) { |
2748 | + if (this === undefined || this === null) throw TypeError(); |
2749 | + var t = Object(this); |
2750 | + var len = ToUint32(t.length); |
2751 | + var tmp = Array(len); |
2752 | + for (var i = 0; i < len; ++i) |
2753 | + tmp[i] = t._getter(i); |
2754 | + return tmp.join(separator === undefined ? ',' : separator); // Hack for IE7 |
2755 | + }}); |
2756 | + |
2757 | + // %TypedArray%.prototype.keys ( ) |
2758 | + // -- defined in es6.js to shim browsers w/ native TypedArrays |
2759 | + |
2760 | + // %TypedArray%.prototype.lastIndexOf ( searchElement, fromIndex = this.length-1 ) |
2761 | + Object.defineProperty($TypedArray$.prototype, 'lastIndexOf', {value: function(searchElement) { |
2762 | + if (this === undefined || this === null) throw TypeError(); |
2763 | + var t = Object(this); |
2764 | + var len = ToUint32(t.length); |
2765 | + if (len === 0) return -1; |
2766 | + var n = len; |
2767 | + if (arguments.length > 1) { |
2768 | + n = Number(arguments[1]); |
2769 | + if (n !== n) { |
2770 | + n = 0; |
2771 | + } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { |
2772 | + n = (n > 0 || -1) * floor(abs(n)); |
2773 | + } |
2774 | + } |
2775 | + var k = n >= 0 ? min(n, len - 1) : len - abs(n); |
2776 | + for (; k >= 0; k--) { |
2777 | + if (t._getter(k) === searchElement) |
2778 | + return k; |
2779 | + } |
2780 | + return -1; |
2781 | + }}); |
2782 | + |
2783 | + // get %TypedArray%.prototype.length |
2784 | + // -- applied directly to the object in the constructor |
2785 | + |
2786 | + // %TypedArray%.prototype.map ( callbackfn, thisArg = undefined ) |
2787 | + Object.defineProperty($TypedArray$.prototype, 'map', {value: function(callbackfn) { |
2788 | + if (this === undefined || this === null) throw TypeError(); |
2789 | + var t = Object(this); |
2790 | + var len = ToUint32(t.length); |
2791 | + if (!IsCallable(callbackfn)) throw TypeError(); |
2792 | + var res = []; res.length = len; |
2793 | + var thisp = arguments[1]; |
2794 | + for (var i = 0; i < len; i++) |
2795 | + res[i] = callbackfn.call(thisp, t._getter(i), i, t); |
2796 | + return new this.constructor(res); |
2797 | + }}); |
2798 | + |
2799 | + // %TypedArray%.prototype.reduce ( callbackfn [, initialValue] ) |
2800 | + Object.defineProperty($TypedArray$.prototype, 'reduce', {value: function(callbackfn) { |
2801 | + if (this === undefined || this === null) throw TypeError(); |
2802 | + var t = Object(this); |
2803 | + var len = ToUint32(t.length); |
2804 | + if (!IsCallable(callbackfn)) throw TypeError(); |
2805 | + // no value to return if no initial value and an empty array |
2806 | + if (len === 0 && arguments.length === 1) throw TypeError(); |
2807 | + var k = 0; |
2808 | + var accumulator; |
2809 | + if (arguments.length >= 2) { |
2810 | + accumulator = arguments[1]; |
2811 | + } else { |
2812 | + accumulator = t._getter(k++); |
2813 | + } |
2814 | + while (k < len) { |
2815 | + accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t); |
2816 | + k++; |
2817 | + } |
2818 | + return accumulator; |
2819 | + }}); |
2820 | + |
2821 | + // %TypedArray%.prototype.reduceRight ( callbackfn [, initialValue] ) |
2822 | + Object.defineProperty($TypedArray$.prototype, 'reduceRight', {value: function(callbackfn) { |
2823 | + if (this === undefined || this === null) throw TypeError(); |
2824 | + var t = Object(this); |
2825 | + var len = ToUint32(t.length); |
2826 | + if (!IsCallable(callbackfn)) throw TypeError(); |
2827 | + // no value to return if no initial value, empty array |
2828 | + if (len === 0 && arguments.length === 1) throw TypeError(); |
2829 | + var k = len - 1; |
2830 | + var accumulator; |
2831 | + if (arguments.length >= 2) { |
2832 | + accumulator = arguments[1]; |
2833 | + } else { |
2834 | + accumulator = t._getter(k--); |
2835 | + } |
2836 | + while (k >= 0) { |
2837 | + accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t); |
2838 | + k--; |
2839 | + } |
2840 | + return accumulator; |
2841 | + }}); |
2842 | + |
2843 | + // %TypedArray%.prototype.reverse ( ) |
2844 | + Object.defineProperty($TypedArray$.prototype, 'reverse', {value: function() { |
2845 | + if (this === undefined || this === null) throw TypeError(); |
2846 | + var t = Object(this); |
2847 | + var len = ToUint32(t.length); |
2848 | + var half = floor(len / 2); |
2849 | + for (var i = 0, j = len - 1; i < half; ++i, --j) { |
2850 | + var tmp = t._getter(i); |
2851 | + t._setter(i, t._getter(j)); |
2852 | + t._setter(j, tmp); |
2853 | + } |
2854 | + return t; |
2855 | + }}); |
2856 | + |
2857 | + // %TypedArray%.prototype.set(array, offset = 0 ) |
2858 | + // %TypedArray%.prototype.set(typedArray, offset = 0 ) |
2859 | + // WebIDL: void set(TypedArray array, optional unsigned long offset); |
2860 | + // WebIDL: void set(sequence<type> array, optional unsigned long offset); |
2861 | + Object.defineProperty($TypedArray$.prototype, 'set', {value: function(index, value) { |
2862 | + if (arguments.length < 1) throw SyntaxError('Not enough arguments'); |
2863 | + var array, sequence, offset, len, |
2864 | + i, s, d, |
2865 | + byteOffset, byteLength, tmp; |
2866 | + |
2867 | + if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) { |
2868 | + // void set(TypedArray array, optional unsigned long offset); |
2869 | + array = arguments[0]; |
2870 | + offset = ToUint32(arguments[1]); |
2871 | + |
2872 | + if (offset + array.length > this.length) { |
2873 | + throw RangeError('Offset plus length of array is out of range'); |
2874 | + } |
2875 | + |
2876 | + byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT; |
2877 | + byteLength = array.length * this.BYTES_PER_ELEMENT; |
2878 | + |
2879 | + if (array.buffer === this.buffer) { |
2880 | + tmp = []; |
2881 | + for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) { |
2882 | + tmp[i] = array.buffer._bytes[s]; |
2883 | + } |
2884 | + for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) { |
2885 | + this.buffer._bytes[d] = tmp[i]; |
2886 | + } |
2887 | + } else { |
2888 | + for (i = 0, s = array.byteOffset, d = byteOffset; |
2889 | + i < byteLength; i += 1, s += 1, d += 1) { |
2890 | + this.buffer._bytes[d] = array.buffer._bytes[s]; |
2891 | + } |
2892 | + } |
2893 | + } else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') { |
2894 | + // void set(sequence<type> array, optional unsigned long offset); |
2895 | + sequence = arguments[0]; |
2896 | + len = ToUint32(sequence.length); |
2897 | + offset = ToUint32(arguments[1]); |
2898 | + |
2899 | + if (offset + len > this.length) { |
2900 | + throw RangeError('Offset plus length of array is out of range'); |
2901 | + } |
2902 | + |
2903 | + for (i = 0; i < len; i += 1) { |
2904 | + s = sequence[i]; |
2905 | + this._setter(offset + i, Number(s)); |
2906 | + } |
2907 | + } else { |
2908 | + throw TypeError('Unexpected argument type(s)'); |
2909 | + } |
2910 | + }}); |
2911 | + |
2912 | + // %TypedArray%.prototype.slice ( start, end ) |
2913 | + Object.defineProperty($TypedArray$.prototype, 'slice', {value: function(start, end) { |
2914 | + var o = ToObject(this); |
2915 | + var lenVal = o.length; |
2916 | + var len = ToUint32(lenVal); |
2917 | + var relativeStart = ToInt32(start); |
2918 | + var k = (relativeStart < 0) ? max(len + relativeStart, 0) : min(relativeStart, len); |
2919 | + var relativeEnd = (end === undefined) ? len : ToInt32(end); |
2920 | + var final = (relativeEnd < 0) ? max(len + relativeEnd, 0) : min(relativeEnd, len); |
2921 | + var count = final - k; |
2922 | + var c = o.constructor; |
2923 | + var a = new c(count); |
2924 | + var n = 0; |
2925 | + while (k < final) { |
2926 | + var kValue = o._getter(k); |
2927 | + a._setter(n, kValue); |
2928 | + ++k; |
2929 | + ++n; |
2930 | + } |
2931 | + return a; |
2932 | + }}); |
2933 | + |
2934 | + // %TypedArray%.prototype.some ( callbackfn, thisArg = undefined ) |
2935 | + Object.defineProperty($TypedArray$.prototype, 'some', {value: function(callbackfn) { |
2936 | + if (this === undefined || this === null) throw TypeError(); |
2937 | + var t = Object(this); |
2938 | + var len = ToUint32(t.length); |
2939 | + if (!IsCallable(callbackfn)) throw TypeError(); |
2940 | + var thisp = arguments[1]; |
2941 | + for (var i = 0; i < len; i++) { |
2942 | + if (callbackfn.call(thisp, t._getter(i), i, t)) { |
2943 | + return true; |
2944 | + } |
2945 | + } |
2946 | + return false; |
2947 | + }}); |
2948 | + |
2949 | + // %TypedArray%.prototype.sort ( comparefn ) |
2950 | + Object.defineProperty($TypedArray$.prototype, 'sort', {value: function(comparefn) { |
2951 | + if (this === undefined || this === null) throw TypeError(); |
2952 | + var t = Object(this); |
2953 | + var len = ToUint32(t.length); |
2954 | + var tmp = Array(len); |
2955 | + for (var i = 0; i < len; ++i) |
2956 | + tmp[i] = t._getter(i); |
2957 | + if (comparefn) tmp.sort(comparefn); else tmp.sort(); // Hack for IE8/9 |
2958 | + for (i = 0; i < len; ++i) |
2959 | + t._setter(i, tmp[i]); |
2960 | + return t; |
2961 | + }}); |
2962 | + |
2963 | + // %TypedArray%.prototype.subarray(begin = 0, end = this.length ) |
2964 | + // WebIDL: TypedArray subarray(long begin, optional long end); |
2965 | + Object.defineProperty($TypedArray$.prototype, 'subarray', {value: function(start, end) { |
2966 | + function clamp(v, min, max) { return v < min ? min : v > max ? max : v; } |
2967 | + |
2968 | + start = ToInt32(start); |
2969 | + end = ToInt32(end); |
2970 | + |
2971 | + if (arguments.length < 1) { start = 0; } |
2972 | + if (arguments.length < 2) { end = this.length; } |
2973 | + |
2974 | + if (start < 0) { start = this.length + start; } |
2975 | + if (end < 0) { end = this.length + end; } |
2976 | + |
2977 | + start = clamp(start, 0, this.length); |
2978 | + end = clamp(end, 0, this.length); |
2979 | + |
2980 | + var len = end - start; |
2981 | + if (len < 0) { |
2982 | + len = 0; |
2983 | + } |
2984 | + |
2985 | + return new this.constructor( |
2986 | + this.buffer, this.byteOffset + start * this.BYTES_PER_ELEMENT, len); |
2987 | + }}); |
2988 | + |
2989 | + // %TypedArray%.prototype.toLocaleString ( ) |
2990 | + // %TypedArray%.prototype.toString ( ) |
2991 | + // %TypedArray%.prototype.values ( ) |
2992 | + // %TypedArray%.prototype [ @@iterator ] ( ) |
2993 | + // get %TypedArray%.prototype [ @@toStringTag ] |
2994 | + // -- defined in es6.js to shim browsers w/ native TypedArrays |
2995 | + |
2996 | + function makeTypedArray(elementSize, pack, unpack) { |
2997 | + // Each TypedArray type requires a distinct constructor instance with |
2998 | + // identical logic, which this produces. |
2999 | + var TypedArray = function() { |
3000 | + Object.defineProperty(this, 'constructor', {value: TypedArray}); |
3001 | + $TypedArray$.apply(this, arguments); |
3002 | + makeArrayAccessors(this); |
3003 | + }; |
3004 | + if ('__proto__' in TypedArray) { |
3005 | + TypedArray.__proto__ = $TypedArray$; |
3006 | + } else { |
3007 | + TypedArray.from = $TypedArray$.from; |
3008 | + TypedArray.of = $TypedArray$.of; |
3009 | + } |
3010 | + |
3011 | + TypedArray.BYTES_PER_ELEMENT = elementSize; |
3012 | + |
3013 | + var TypedArrayPrototype = function() {}; |
3014 | + TypedArrayPrototype.prototype = $TypedArrayPrototype$; |
3015 | + |
3016 | + TypedArray.prototype = new TypedArrayPrototype(); |
3017 | + |
3018 | + Object.defineProperty(TypedArray.prototype, 'BYTES_PER_ELEMENT', {value: elementSize}); |
3019 | + Object.defineProperty(TypedArray.prototype, '_pack', {value: pack}); |
3020 | + Object.defineProperty(TypedArray.prototype, '_unpack', {value: unpack}); |
3021 | + |
3022 | + return TypedArray; |
3023 | + } |
3024 | + |
3025 | + var Int8Array = makeTypedArray(1, packI8, unpackI8); |
3026 | + var Uint8Array = makeTypedArray(1, packU8, unpackU8); |
3027 | + var Uint8ClampedArray = makeTypedArray(1, packU8Clamped, unpackU8); |
3028 | + var Int16Array = makeTypedArray(2, packI16, unpackI16); |
3029 | + var Uint16Array = makeTypedArray(2, packU16, unpackU16); |
3030 | + var Int32Array = makeTypedArray(4, packI32, unpackI32); |
3031 | + var Uint32Array = makeTypedArray(4, packU32, unpackU32); |
3032 | + var Float32Array = makeTypedArray(4, packF32, unpackF32); |
3033 | + var Float64Array = makeTypedArray(8, packF64, unpackF64); |
3034 | + |
3035 | + global.Int8Array = global.Int8Array || Int8Array; |
3036 | + global.Uint8Array = global.Uint8Array || Uint8Array; |
3037 | + global.Uint8ClampedArray = global.Uint8ClampedArray || Uint8ClampedArray; |
3038 | + global.Int16Array = global.Int16Array || Int16Array; |
3039 | + global.Uint16Array = global.Uint16Array || Uint16Array; |
3040 | + global.Int32Array = global.Int32Array || Int32Array; |
3041 | + global.Uint32Array = global.Uint32Array || Uint32Array; |
3042 | + global.Float32Array = global.Float32Array || Float32Array; |
3043 | + global.Float64Array = global.Float64Array || Float64Array; |
3044 | + }()); |
3045 | + |
3046 | + // |
3047 | + // 6 The DataView View Type |
3048 | + // |
3049 | + |
3050 | + (function() { |
3051 | + function r(array, index) { |
3052 | + return IsCallable(array.get) ? array.get(index) : array[index]; |
3053 | + } |
3054 | + |
3055 | + var IS_BIG_ENDIAN = (function() { |
3056 | + var u16array = new Uint16Array([0x1234]), |
3057 | + u8array = new Uint8Array(u16array.buffer); |
3058 | + return r(u8array, 0) === 0x12; |
3059 | + }()); |
3060 | + |
3061 | + // DataView(buffer, byteOffset=0, byteLength=undefined) |
3062 | + // WebIDL: Constructor(ArrayBuffer buffer, |
3063 | + // optional unsigned long byteOffset, |
3064 | + // optional unsigned long byteLength) |
3065 | + function DataView(buffer, byteOffset, byteLength) { |
3066 | + if (!(buffer instanceof ArrayBuffer || Class(buffer) === 'ArrayBuffer')) throw TypeError(); |
3067 | + |
3068 | + byteOffset = ToUint32(byteOffset); |
3069 | + if (byteOffset > buffer.byteLength) |
3070 | + throw RangeError('byteOffset out of range'); |
3071 | + |
3072 | + if (byteLength === undefined) |
3073 | + byteLength = buffer.byteLength - byteOffset; |
3074 | + else |
3075 | + byteLength = ToUint32(byteLength); |
3076 | + |
3077 | + if ((byteOffset + byteLength) > buffer.byteLength) |
3078 | + throw RangeError('byteOffset and length reference an area beyond the end of the buffer'); |
3079 | + |
3080 | + Object.defineProperty(this, 'buffer', {value: buffer}); |
3081 | + Object.defineProperty(this, 'byteLength', {value: byteLength}); |
3082 | + Object.defineProperty(this, 'byteOffset', {value: byteOffset}); |
3083 | + }; |
3084 | + |
3085 | + // get DataView.prototype.buffer |
3086 | + // get DataView.prototype.byteLength |
3087 | + // get DataView.prototype.byteOffset |
3088 | + // -- applied directly to instances by the constructor |
3089 | + |
3090 | + function makeGetter(arrayType) { |
3091 | + return function GetViewValue(byteOffset, littleEndian) { |
3092 | + byteOffset = ToUint32(byteOffset); |
3093 | + |
3094 | + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) |
3095 | + throw RangeError('Array index out of range'); |
3096 | + |
3097 | + byteOffset += this.byteOffset; |
3098 | + |
3099 | + var uint8Array = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT), |
3100 | + bytes = []; |
3101 | + for (var i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) |
3102 | + bytes.push(r(uint8Array, i)); |
3103 | + |
3104 | + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) |
3105 | + bytes.reverse(); |
3106 | + |
3107 | + return r(new arrayType(new Uint8Array(bytes).buffer), 0); |
3108 | + }; |
3109 | + } |
3110 | + |
3111 | + Object.defineProperty(DataView.prototype, 'getUint8', {value: makeGetter(Uint8Array)}); |
3112 | + Object.defineProperty(DataView.prototype, 'getInt8', {value: makeGetter(Int8Array)}); |
3113 | + Object.defineProperty(DataView.prototype, 'getUint16', {value: makeGetter(Uint16Array)}); |
3114 | + Object.defineProperty(DataView.prototype, 'getInt16', {value: makeGetter(Int16Array)}); |
3115 | + Object.defineProperty(DataView.prototype, 'getUint32', {value: makeGetter(Uint32Array)}); |
3116 | + Object.defineProperty(DataView.prototype, 'getInt32', {value: makeGetter(Int32Array)}); |
3117 | + Object.defineProperty(DataView.prototype, 'getFloat32', {value: makeGetter(Float32Array)}); |
3118 | + Object.defineProperty(DataView.prototype, 'getFloat64', {value: makeGetter(Float64Array)}); |
3119 | + |
3120 | + function makeSetter(arrayType) { |
3121 | + return function SetViewValue(byteOffset, value, littleEndian) { |
3122 | + byteOffset = ToUint32(byteOffset); |
3123 | + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) |
3124 | + throw RangeError('Array index out of range'); |
3125 | + |
3126 | + // Get bytes |
3127 | + var typeArray = new arrayType([value]), |
3128 | + byteArray = new Uint8Array(typeArray.buffer), |
3129 | + bytes = [], i, byteView; |
3130 | + |
3131 | + for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) |
3132 | + bytes.push(r(byteArray, i)); |
3133 | + |
3134 | + // Flip if necessary |
3135 | + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) |
3136 | + bytes.reverse(); |
3137 | + |
3138 | + // Write them |
3139 | + byteView = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT); |
3140 | + byteView.set(bytes); |
3141 | + }; |
3142 | + } |
3143 | + |
3144 | + Object.defineProperty(DataView.prototype, 'setUint8', {value: makeSetter(Uint8Array)}); |
3145 | + Object.defineProperty(DataView.prototype, 'setInt8', {value: makeSetter(Int8Array)}); |
3146 | + Object.defineProperty(DataView.prototype, 'setUint16', {value: makeSetter(Uint16Array)}); |
3147 | + Object.defineProperty(DataView.prototype, 'setInt16', {value: makeSetter(Int16Array)}); |
3148 | + Object.defineProperty(DataView.prototype, 'setUint32', {value: makeSetter(Uint32Array)}); |
3149 | + Object.defineProperty(DataView.prototype, 'setInt32', {value: makeSetter(Int32Array)}); |
3150 | + Object.defineProperty(DataView.prototype, 'setFloat32', {value: makeSetter(Float32Array)}); |
3151 | + Object.defineProperty(DataView.prototype, 'setFloat64', {value: makeSetter(Float64Array)}); |
3152 | + |
3153 | + global.DataView = global.DataView || DataView; |
3154 | + |
3155 | + }()); |
3156 | + |
3157 | +}(this)); |
3158 | |
3159 | === removed file 'rockworkd/libpebble/jskitmanager.cpp' |
3160 | --- rockworkd/libpebble/jskitmanager.cpp 2016-01-10 22:35:04 +0000 |
3161 | +++ rockworkd/libpebble/jskitmanager.cpp 1970-01-01 00:00:00 +0000 |
3162 | @@ -1,243 +0,0 @@ |
3163 | -#include <QFile> |
3164 | -#include <QDir> |
3165 | - |
3166 | -#include "jskitmanager.h" |
3167 | -#include "jskitobjects.h" |
3168 | - |
3169 | -JSKitManager::JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent) : |
3170 | - QObject(parent), |
3171 | - m_pebble(pebble), |
3172 | - m_connection(connection), _apps(apps), _appmsg(appmsg), _engine(0) |
3173 | -{ |
3174 | - m_configurationUuid = QUuid(); |
3175 | - connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); |
3176 | - connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); |
3177 | -} |
3178 | - |
3179 | -JSKitManager::~JSKitManager() |
3180 | -{ |
3181 | - if (_engine) { |
3182 | - stopJsApp(); |
3183 | - } |
3184 | -} |
3185 | - |
3186 | -QJSEngine * JSKitManager::engine() |
3187 | -{ |
3188 | - return _engine; |
3189 | -} |
3190 | - |
3191 | -bool JSKitManager::isJSKitAppRunning() const |
3192 | -{ |
3193 | - return _engine != 0; |
3194 | -} |
3195 | - |
3196 | -QString JSKitManager::describeError(QJSValue error) |
3197 | -{ |
3198 | - return QString("%1:%2: %3") |
3199 | - .arg(error.property("fileName").toString()) |
3200 | - .arg(error.property("lineNumber").toInt()) |
3201 | - .arg(error.toString()); |
3202 | -} |
3203 | - |
3204 | -void JSKitManager::showConfiguration() |
3205 | -{ |
3206 | - if (_engine) { |
3207 | - qDebug() << "requesting configuration"; |
3208 | - _jspebble->invokeCallbacks("showConfiguration"); |
3209 | - } else { |
3210 | - qWarning() << "requested to show configuration, but JS engine is not running"; |
3211 | - } |
3212 | -} |
3213 | - |
3214 | -void JSKitManager::handleWebviewClosed(const QString &result) |
3215 | -{ |
3216 | - if (_engine) { |
3217 | - QJSValue eventObj = _engine->newObject(); |
3218 | - QByteArray data = QByteArray::fromPercentEncoding(result.toUtf8()); |
3219 | - eventObj.setProperty("response", _engine->toScriptValue(data)); |
3220 | - |
3221 | - qDebug() << "Sending" << eventObj.property("response").toString(); |
3222 | - |
3223 | - |
3224 | - _jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj})); |
3225 | -// _jspebble->invokeCallbacks("webviewclosed", eventObj); |
3226 | - } else { |
3227 | - qWarning() << "webview closed event, but JS engine is not running"; |
3228 | - } |
3229 | -} |
3230 | - |
3231 | -void JSKitManager::setConfigurationId(const QUuid &uuid) |
3232 | -{ |
3233 | - m_configurationUuid = uuid; |
3234 | -} |
3235 | - |
3236 | -AppInfo JSKitManager::currentApp() |
3237 | -{ |
3238 | - return _curApp; |
3239 | -} |
3240 | - |
3241 | -void JSKitManager::handleAppStarted(const QUuid &uuid) |
3242 | -{ |
3243 | - qDebug() << "handleAppStarted!!!" << uuid; |
3244 | - AppInfo info = _apps->info(uuid); |
3245 | - if (!info.uuid().isNull() && info.isJSKit()) { |
3246 | - qDebug() << "Preparing to start JSKit app" << info.uuid() << info.shortName(); |
3247 | - _curApp = info; |
3248 | - startJsApp(); |
3249 | - } |
3250 | -} |
3251 | - |
3252 | -void JSKitManager::handleAppStopped(const QUuid &uuid) |
3253 | -{ |
3254 | - if (!_curApp.uuid().isNull()) { |
3255 | - if (_curApp.uuid() != uuid) { |
3256 | - qWarning() << "Closed app with invalid UUID"; |
3257 | - } |
3258 | - |
3259 | - stopJsApp(); |
3260 | - _curApp = AppInfo(); |
3261 | - qDebug() << "App stopped" << uuid; |
3262 | - } |
3263 | -} |
3264 | - |
3265 | -void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &msg) |
3266 | -{ |
3267 | - qDebug() << "handleAppMessage" << uuid << msg; |
3268 | - if (_curApp.uuid() == uuid) { |
3269 | - qDebug() << "received a message for the current JSKit app"; |
3270 | - |
3271 | - if (!_engine) { |
3272 | - qDebug() << "but engine is stopped"; |
3273 | - return; |
3274 | - } |
3275 | - |
3276 | - QJSValue eventObj = _engine->newObject(); |
3277 | - eventObj.setProperty("payload", _engine->toScriptValue(msg)); |
3278 | - |
3279 | - _jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj})); |
3280 | - } |
3281 | -} |
3282 | - |
3283 | -bool JSKitManager::loadJsFile(const QString &filename) |
3284 | -{ |
3285 | - Q_ASSERT(_engine); |
3286 | - |
3287 | - QFile file(filename); |
3288 | - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { |
3289 | - qWarning() << "Failed to load JS file:" << file.fileName(); |
3290 | - return false; |
3291 | - } |
3292 | - |
3293 | - qDebug() << "now parsing" << file.fileName(); |
3294 | - |
3295 | - QJSValue result = _engine->evaluate(QString::fromUtf8(file.readAll()), file.fileName()); |
3296 | - if (result.isError()) { |
3297 | - qWarning() << "error while evaluating JS script:" << describeError(result); |
3298 | - return false; |
3299 | - } |
3300 | - |
3301 | - qDebug() << "JS script evaluated"; |
3302 | - |
3303 | - return true; |
3304 | -} |
3305 | - |
3306 | -void JSKitManager::startJsApp() |
3307 | -{ |
3308 | - qDebug() << "startJsApp called"; |
3309 | - |
3310 | - if (_engine) stopJsApp(); |
3311 | - |
3312 | - if (_curApp.uuid().isNull()) { |
3313 | - qWarning() << "Attempting to start JS app with invalid UUID"; |
3314 | - return; |
3315 | - } |
3316 | - |
3317 | - _engine = new QJSEngine(this); |
3318 | - _jspebble = new JSKitPebble(_curApp, this, _engine); |
3319 | - _jsconsole = new JSKitConsole(_engine); |
3320 | - _jsstorage = new JSKitLocalStorage(m_pebble->storagePath(), _curApp.uuid(), _engine); |
3321 | - _jsgeo = new JSKitGeolocation(this, _engine); |
3322 | - |
3323 | - qDebug() << "starting JS app" << _curApp.shortName(); |
3324 | - |
3325 | - QJSValue globalObj = _engine->globalObject(); |
3326 | - |
3327 | - globalObj.setProperty("RealPebble", _engine->newQObject(_jspebble)); |
3328 | - globalObj.setProperty("console", _engine->newQObject(_jsconsole)); |
3329 | - globalObj.setProperty("localStorage", _engine->newQObject(_jsstorage)); |
3330 | - |
3331 | - QJSValue navigatorObj = _engine->newObject(); |
3332 | - navigatorObj.setProperty("RealGeolocation", _engine->newQObject(_jsgeo)); |
3333 | - navigatorObj.setProperty("language", _engine->toScriptValue(QLocale().name())); |
3334 | - globalObj.setProperty("navigator", navigatorObj); |
3335 | - |
3336 | - // Set this.window = this |
3337 | - globalObj.setProperty("window", globalObj); |
3338 | - |
3339 | - // Shims for compatibility... |
3340 | - QJSValue result = _engine->evaluate( |
3341 | - "function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }\n\ |
3342 | - function setInterval(func, time) { return Pebble.setInterval(func, time); }\n\ |
3343 | - function clearInterval(id) { Pebble.clearInterval(id); }\n\ |
3344 | - function setTimeout(func, time) { return Pebble.setTimeout(func, time); }\n\ |
3345 | - function clearTimeout(id) { Pebble.clearTimeout(id); }\n\ |
3346 | - Pebble = {}; for (var key in RealPebble) {Pebble[key] = RealPebble[key];}\n\ |
3347 | - navigator.geolocation = {}; for (var key in navigator.RealGeolocation) {navigator.geolocation[key] = navigator.RealGeolocation[key];}\n\ |
3348 | - "); |
3349 | - qDebug() << result.toString(); |
3350 | - Q_ASSERT(!result.isError()); |
3351 | - |
3352 | - // Polyfills... |
3353 | - loadJsFile(":/typedarray.js"); |
3354 | - |
3355 | - // Now the actual script |
3356 | - QString jsApp = _curApp.file(AppInfo::FileTypeJsApp, HardwarePlatformUnknown); |
3357 | - QFile f(jsApp); |
3358 | - if (!f.open(QFile::ReadOnly)) { |
3359 | - qWarning() << "Error opening" << jsApp; |
3360 | - return; |
3361 | - } |
3362 | - QJSValue ret = _engine->evaluate(QString::fromUtf8(f.readAll())); |
3363 | - qDebug() << "loaded script" << ret.toString(); |
3364 | - |
3365 | - // Setup the message callback |
3366 | - QUuid uuid = _curApp.uuid(); |
3367 | - _appmsg->setMessageHandler(uuid, [this, uuid](const QVariantMap &msg) { |
3368 | - QMetaObject::invokeMethod(this, "handleAppMessage", Qt::QueuedConnection, |
3369 | - Q_ARG(QUuid, uuid), |
3370 | - Q_ARG(QVariantMap, msg)); |
3371 | - |
3372 | - // Invoke the slot as a queued connection to give time for the ACK message |
3373 | - // to go through first. |
3374 | - |
3375 | - return true; |
3376 | - }); |
3377 | - |
3378 | - // We try to invoke the callbacks even if script parsing resulted in error... |
3379 | - qDebug() << "calling ready!"; |
3380 | - _jspebble->invokeCallbacks("ready"); |
3381 | - |
3382 | - if (m_configurationUuid == _curApp.uuid()) { |
3383 | - qDebug() << "going to launch config for" << m_configurationUuid; |
3384 | - showConfiguration(); |
3385 | - } |
3386 | - |
3387 | - m_configurationUuid = QUuid(); |
3388 | -} |
3389 | - |
3390 | -void JSKitManager::stopJsApp() |
3391 | -{ |
3392 | - qDebug() << "stop js app"; |
3393 | - if (!_engine) return; // Nothing to do! |
3394 | - |
3395 | - qDebug() << "stopping JS app"; |
3396 | - |
3397 | - if (!_curApp.uuid().isNull()) { |
3398 | - _appmsg->clearMessageHandler(_curApp.uuid()); |
3399 | - } |
3400 | - |
3401 | - _engine->collectGarbage(); |
3402 | - |
3403 | - _engine->deleteLater(); |
3404 | - _engine = 0; |
3405 | -} |
3406 | |
3407 | === removed file 'rockworkd/libpebble/jskitmanager.h' |
3408 | --- rockworkd/libpebble/jskitmanager.h 2016-01-03 05:06:13 +0000 |
3409 | +++ rockworkd/libpebble/jskitmanager.h 1970-01-01 00:00:00 +0000 |
3410 | @@ -1,65 +0,0 @@ |
3411 | -#ifndef JSKITMANAGER_H |
3412 | -#define JSKITMANAGER_H |
3413 | - |
3414 | -#include <QJSEngine> |
3415 | -#include <QPointer> |
3416 | -#include "appmanager.h" |
3417 | -#include "watchconnection.h" |
3418 | -#include "pebble.h" |
3419 | -#include "appmsgmanager.h" |
3420 | -//#include "settings.h" |
3421 | - |
3422 | -class JSKitPebble; |
3423 | -class JSKitConsole; |
3424 | -class JSKitLocalStorage; |
3425 | -class JSKitGeolocation; |
3426 | - |
3427 | -class JSKitManager : public QObject |
3428 | -{ |
3429 | - Q_OBJECT |
3430 | - |
3431 | -public: |
3432 | - explicit JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); |
3433 | - ~JSKitManager(); |
3434 | - |
3435 | - QJSEngine * engine(); |
3436 | - bool isJSKitAppRunning() const; |
3437 | - |
3438 | - static QString describeError(QJSValue error); |
3439 | - |
3440 | - void showConfiguration(); |
3441 | - void handleWebviewClosed(const QString &result); |
3442 | - void setConfigurationId(const QUuid &uuid); |
3443 | - AppInfo currentApp(); |
3444 | - |
3445 | -signals: |
3446 | - void appNotification(const QUuid &uuid, const QString &title, const QString &body); |
3447 | - void openURL(const QString &uuid, const QString &url); |
3448 | - |
3449 | -private slots: |
3450 | - void handleAppStarted(const QUuid &uuid); |
3451 | - void handleAppStopped(const QUuid &uuid); |
3452 | - void handleAppMessage(const QUuid &uuid, const QVariantMap &msg); |
3453 | - |
3454 | -private: |
3455 | - bool loadJsFile(const QString &filename); |
3456 | - void startJsApp(); |
3457 | - void stopJsApp(); |
3458 | - |
3459 | -private: |
3460 | - friend class JSKitPebble; |
3461 | - |
3462 | - Pebble *m_pebble; |
3463 | - WatchConnection *m_connection; |
3464 | - AppManager *_apps; |
3465 | - AppMsgManager *_appmsg; |
3466 | - AppInfo _curApp; |
3467 | - QJSEngine *_engine; |
3468 | - QPointer<JSKitPebble> _jspebble; |
3469 | - QPointer<JSKitConsole> _jsconsole; |
3470 | - QPointer<JSKitLocalStorage> _jsstorage; |
3471 | - QPointer<JSKitGeolocation> _jsgeo; |
3472 | - QUuid m_configurationUuid; |
3473 | -}; |
3474 | - |
3475 | -#endif // JSKITMANAGER_H |
3476 | |
3477 | === removed file 'rockworkd/libpebble/jskitobjects.cpp' |
3478 | --- rockworkd/libpebble/jskitobjects.cpp 2016-01-10 22:35:04 +0000 |
3479 | +++ rockworkd/libpebble/jskitobjects.cpp 1970-01-01 00:00:00 +0000 |
3480 | @@ -1,859 +0,0 @@ |
3481 | -#include <QDesktopServices> |
3482 | -#include <QUrl> |
3483 | -#include <QAuthenticator> |
3484 | -#include <QBuffer> |
3485 | -#include <QDir> |
3486 | -#include <QTimerEvent> |
3487 | -#include <QCryptographicHash> |
3488 | -#include <limits> |
3489 | -#include "jskitobjects.h" |
3490 | - |
3491 | -static const char *token_salt = "0feeb7416d3c4546a19b04bccd8419b1"; |
3492 | - |
3493 | -JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr, QObject *parent) |
3494 | - : QObject(parent), _appInfo(info), _mgr(mgr) |
3495 | -{ |
3496 | -} |
3497 | - |
3498 | -void JSKitPebble::addEventListener(const QString &type, QJSValue function) |
3499 | -{ |
3500 | - qDebug() << "Adding event listener for " << type; |
3501 | - _callbacks[type].append(function); |
3502 | -} |
3503 | - |
3504 | -void JSKitPebble::removeEventListener(const QString &type, QJSValue function) |
3505 | -{ |
3506 | - if (!_callbacks.contains(type)) return; |
3507 | - QList<QJSValue> &callbacks = _callbacks[type]; |
3508 | - |
3509 | - for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ) { |
3510 | - if (it->strictlyEquals(function)) { |
3511 | - it = callbacks.erase(it); |
3512 | - } else { |
3513 | - ++it; |
3514 | - } |
3515 | - } |
3516 | - |
3517 | - if (callbacks.empty()) { |
3518 | - _callbacks.remove(type); |
3519 | - } |
3520 | -} |
3521 | - |
3522 | -int JSKitPebble::setInterval(QJSValue expression, int delay) |
3523 | -{ |
3524 | - qDebug() << "Setting interval for " << delay << "ms: " << expression.toString(); |
3525 | - if (expression.isString() || expression.isCallable()) { |
3526 | - int timerId = startTimer(delay); |
3527 | - _intervals.insert(timerId, expression); |
3528 | - qDebug() << "Timer id: " << timerId; |
3529 | - return timerId; |
3530 | - } |
3531 | - return -1; |
3532 | -} |
3533 | - |
3534 | -void JSKitPebble::clearInterval(int timerId) |
3535 | -{ |
3536 | - qDebug() << "Killing interval " << timerId ; |
3537 | - killTimer(timerId); |
3538 | - _intervals.remove(timerId); |
3539 | -} |
3540 | - |
3541 | -int JSKitPebble::setTimeout(QJSValue expression, int delay) |
3542 | -{ |
3543 | - qDebug() << "Setting timeout for " << delay << "ms: " << expression.toString(); |
3544 | - if (expression.isString() || expression.isCallable()) { |
3545 | - int timerId = startTimer(delay); |
3546 | - _timeouts.insert(timerId, expression); |
3547 | - return timerId; |
3548 | - } |
3549 | - return -1; |
3550 | -} |
3551 | - |
3552 | -void JSKitPebble::clearTimeout(int timerId) |
3553 | -{ |
3554 | - qDebug() << "Killing timeout " << timerId ; |
3555 | - killTimer(timerId); |
3556 | - _timeouts.remove(timerId); |
3557 | -} |
3558 | - |
3559 | -void JSKitPebble::timerEvent(QTimerEvent *event) |
3560 | -{ |
3561 | - int id = event->timerId(); |
3562 | - QJSValue expression; // find in either intervals or timeouts |
3563 | - if (_intervals.contains(id)) |
3564 | - expression = _intervals.value(id); |
3565 | - else if (_timeouts.contains(id)) { |
3566 | - expression = _timeouts.value(id); |
3567 | - killTimer(id); // timeouts don't repeat |
3568 | - } |
3569 | - else { |
3570 | - qWarning() << "Unknown timer event"; |
3571 | - killTimer(id); // interval nor timeout exist. kill the timer |
3572 | - return; |
3573 | - } |
3574 | - |
3575 | - if (expression.isCallable()) { // call it if it's a function |
3576 | - QJSValue result = expression.call().toString(); |
3577 | - qDebug() << "Timer function result: " << result.toString(); |
3578 | - } |
3579 | - else { // otherwise evaluate it |
3580 | - QJSValue result = _mgr->engine()->evaluate(expression.toString()); |
3581 | - qDebug() << "Timer expression result: " << result.toString(); |
3582 | - } |
3583 | -} |
3584 | - |
3585 | -uint JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) |
3586 | -{ |
3587 | - QVariantMap data = message.toVariant().toMap(); |
3588 | - QPointer<JSKitPebble> pebbObj = this; |
3589 | - uint transactionId = _mgr->_appmsg->nextTransactionId(); |
3590 | - |
3591 | - qDebug() << "sendAppMessage" << data; |
3592 | - |
3593 | - _mgr->_appmsg->send(_appInfo.uuid(), data, |
3594 | - [pebbObj, transactionId, callbackForAck]() mutable { |
3595 | - if (pebbObj.isNull()) return; |
3596 | - if (callbackForAck.isCallable()) { |
3597 | - qDebug() << "Invoking ack callback"; |
3598 | - QJSValue event = pebbObj->buildAckEventObject(transactionId); |
3599 | - QJSValue result = callbackForAck.call(QJSValueList({event})); |
3600 | - if (result.isError()) { |
3601 | - qWarning() << "error while invoking ACK callback" << callbackForAck.toString() << ":" |
3602 | - << JSKitManager::describeError(result); |
3603 | - } |
3604 | - } else { |
3605 | - qDebug() << "Ack callback not callable"; |
3606 | - } |
3607 | - }, |
3608 | - [pebbObj, transactionId, callbackForNack]() mutable { |
3609 | - if (pebbObj.isNull()) return; |
3610 | - if (callbackForNack.isCallable()) { |
3611 | - qDebug() << "Invoking nack callback"; |
3612 | - QJSValue event = pebbObj->buildAckEventObject(transactionId, "NACK from watch"); |
3613 | - QJSValue result = callbackForNack.call(QJSValueList({event})); |
3614 | - if (result.isError()) { |
3615 | - qWarning() << "error while invoking NACK callback" << callbackForNack.toString() << ":" |
3616 | - << JSKitManager::describeError(result); |
3617 | - } |
3618 | - } else { |
3619 | - qDebug() << "Nack callback not callable"; |
3620 | - } |
3621 | - }); |
3622 | - |
3623 | - return transactionId; |
3624 | -} |
3625 | - |
3626 | -void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body) |
3627 | -{ |
3628 | - qDebug() << "showSimpleNotificationOnPebble" << title << body; |
3629 | - emit _mgr->appNotification(_appInfo.uuid(), title, body); |
3630 | -} |
3631 | - |
3632 | -void JSKitPebble::openURL(const QUrl &url) |
3633 | -{ |
3634 | - qDebug() << "opening url" << url.toString(); |
3635 | - emit _mgr->openURL(_appInfo.uuid().toString(), url.toString()); |
3636 | -} |
3637 | - |
3638 | -QString JSKitPebble::getAccountToken() const |
3639 | -{ |
3640 | - // We do not have any account system, so we just fake something up. |
3641 | - QCryptographicHash hasher(QCryptographicHash::Md5); |
3642 | - |
3643 | - hasher.addData(token_salt, strlen(token_salt)); |
3644 | - hasher.addData(_appInfo.uuid().toByteArray()); |
3645 | - |
3646 | - QSettings settings; |
3647 | - QString token = settings.value("accountToken").toString(); |
3648 | -// QString token = _mgr->_settings->property("accountToken").toString(); |
3649 | - if (token.isEmpty()) { |
3650 | - token = QUuid::createUuid().toString(); |
3651 | - qDebug() << "created new account token" << token; |
3652 | - settings.setValue("accountToken", token); |
3653 | -// _mgr->_settings->setProperty("accountToken", token); |
3654 | - } |
3655 | - hasher.addData(token.toLatin1()); |
3656 | - |
3657 | - QString hash = hasher.result().toHex(); |
3658 | - qDebug() << "returning account token" << hash; |
3659 | - |
3660 | - return hash; |
3661 | -} |
3662 | - |
3663 | -QString JSKitPebble::getWatchToken() const |
3664 | -{ |
3665 | - QCryptographicHash hasher(QCryptographicHash::Md5); |
3666 | - |
3667 | - hasher.addData(token_salt, strlen(token_salt)); |
3668 | - hasher.addData(_appInfo.uuid().toByteArray()); |
3669 | - hasher.addData(_mgr->m_pebble->serialNumber().toLatin1()); |
3670 | - |
3671 | - QString hash = hasher.result().toHex(); |
3672 | - qDebug() << "returning watch token" << hash; |
3673 | - |
3674 | - return hash; |
3675 | -} |
3676 | - |
3677 | -QJSValue JSKitPebble::createXMLHttpRequest() |
3678 | -{ |
3679 | - JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(_mgr, 0); |
3680 | - // Should be deleted by JS engine. |
3681 | - return _mgr->engine()->newQObject(xhr); |
3682 | -} |
3683 | - |
3684 | -QJSValue JSKitPebble::buildAckEventObject(uint transaction, const QString &message) const |
3685 | -{ |
3686 | - QJSEngine *engine = _mgr->engine(); |
3687 | - QJSValue eventObj = engine->newObject(); |
3688 | - QJSValue dataObj = engine->newObject(); |
3689 | - |
3690 | - dataObj.setProperty("transactionId", engine->toScriptValue(transaction)); |
3691 | - eventObj.setProperty("data", dataObj); |
3692 | - |
3693 | - if (!message.isEmpty()) { |
3694 | - QJSValue errorObj = engine->newObject(); |
3695 | - errorObj.setProperty("message", engine->toScriptValue(message)); |
3696 | - eventObj.setProperty("error", errorObj); |
3697 | - } |
3698 | - |
3699 | - return eventObj; |
3700 | -} |
3701 | - |
3702 | -void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) |
3703 | -{ |
3704 | - if (!_callbacks.contains(type)) return; |
3705 | - QList<QJSValue> &callbacks = _callbacks[type]; |
3706 | - |
3707 | - for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ++it) { |
3708 | - qDebug() << "invoking callback" << type << it->toString(); |
3709 | - QJSValue result = it->call(args); |
3710 | - if (result.isError()) { |
3711 | - qWarning() << "error while invoking callback" << type << it->toString() << ":" |
3712 | - << JSKitManager::describeError(result); |
3713 | - } |
3714 | - } |
3715 | -} |
3716 | - |
3717 | -JSKitConsole::JSKitConsole(QObject *parent) |
3718 | - : QObject(parent), l(metaObject()->className()) |
3719 | -{ |
3720 | -} |
3721 | - |
3722 | -void JSKitConsole::log(const QString &msg) |
3723 | -{ |
3724 | - qCDebug(l) << msg; |
3725 | -} |
3726 | - |
3727 | -void JSKitConsole::warn(const QString &msg) |
3728 | -{ |
3729 | - qCWarning(l) << msg; |
3730 | -} |
3731 | - |
3732 | -void JSKitConsole::error(const QString &msg) |
3733 | -{ |
3734 | - qCCritical(l) << msg; |
3735 | -} |
3736 | - |
3737 | -void JSKitConsole::info(const QString &msg) |
3738 | -{ |
3739 | - qCDebug(l) << msg; |
3740 | -} |
3741 | - |
3742 | -JSKitLocalStorage::JSKitLocalStorage(const QString &storagePath, const QUuid &uuid, QObject *parent): |
3743 | - QObject(parent), |
3744 | - _storage(new QSettings(getStorageFileFor(storagePath, uuid), QSettings::IniFormat, this)) |
3745 | -{ |
3746 | - _len = _storage->allKeys().size(); |
3747 | -} |
3748 | - |
3749 | -int JSKitLocalStorage::length() const |
3750 | -{ |
3751 | - return _len; |
3752 | -} |
3753 | - |
3754 | -QJSValue JSKitLocalStorage::getItem(const QString &key) const |
3755 | -{ |
3756 | - QVariant value = _storage->value(key); |
3757 | - if (value.isValid()) { |
3758 | - return QJSValue(value.toString()); |
3759 | - } else { |
3760 | - return QJSValue(QJSValue::NullValue); |
3761 | - } |
3762 | -} |
3763 | - |
3764 | -void JSKitLocalStorage::setItem(const QString &key, const QString &value) |
3765 | -{ |
3766 | - _storage->setValue(key, QVariant::fromValue(value)); |
3767 | - checkLengthChanged(); |
3768 | -} |
3769 | - |
3770 | -void JSKitLocalStorage::removeItem(const QString &key) |
3771 | -{ |
3772 | - _storage->remove(key); |
3773 | - checkLengthChanged(); |
3774 | -} |
3775 | - |
3776 | -void JSKitLocalStorage::clear() |
3777 | -{ |
3778 | - _storage->clear(); |
3779 | - _len = 0; |
3780 | - emit lengthChanged(); |
3781 | -} |
3782 | - |
3783 | -void JSKitLocalStorage::checkLengthChanged() |
3784 | -{ |
3785 | - int curLen = _storage->allKeys().size(); |
3786 | - if (_len != curLen) { |
3787 | - _len = curLen; |
3788 | - emit lengthChanged(); |
3789 | - } |
3790 | -} |
3791 | - |
3792 | -QString JSKitLocalStorage::getStorageFileFor(const QString &storageDir, const QUuid &uuid) |
3793 | -{ |
3794 | - QDir dataDir(storageDir + "/js-storage"); |
3795 | - if (!dataDir.exists() && !dataDir.mkpath(dataDir.absolutePath())) { |
3796 | - qWarning() << "Error creating jskit storage dir"; |
3797 | - return QString(); |
3798 | - } |
3799 | - QString fileName = uuid.toString(); |
3800 | - fileName.remove('{'); |
3801 | - fileName.remove('}'); |
3802 | - return dataDir.absoluteFilePath(fileName + ".ini"); |
3803 | -} |
3804 | - |
3805 | -JSKitXMLHttpRequest::JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent) |
3806 | - : QObject(parent), l(metaObject()->className()), _mgr(mgr), |
3807 | - _net(new QNetworkAccessManager(this)), _timeout(0), _reply(0) |
3808 | -{ |
3809 | - qCDebug(l) << "constructed"; |
3810 | - connect(_net, &QNetworkAccessManager::authenticationRequired, |
3811 | - this, &JSKitXMLHttpRequest::handleAuthenticationRequired); |
3812 | -} |
3813 | - |
3814 | -JSKitXMLHttpRequest::~JSKitXMLHttpRequest() |
3815 | -{ |
3816 | - qCDebug(l) << "destructed"; |
3817 | -} |
3818 | - |
3819 | -void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool async, const QString &username, const QString &password) |
3820 | -{ |
3821 | - if (_reply) { |
3822 | - _reply->deleteLater(); |
3823 | - _reply = 0; |
3824 | - } |
3825 | - |
3826 | - _username = username; |
3827 | - _password = password; |
3828 | - _request = QNetworkRequest(QUrl(url)); |
3829 | - _verb = method; |
3830 | - Q_UNUSED(async); |
3831 | - |
3832 | - qCDebug(l) << "opened to URL" << _request.url().toString(); |
3833 | -} |
3834 | - |
3835 | -void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString &value) |
3836 | -{ |
3837 | - qCDebug(l) << "setRequestHeader" << header << value; |
3838 | - _request.setRawHeader(header.toLatin1(), value.toLatin1()); |
3839 | -} |
3840 | - |
3841 | -void JSKitXMLHttpRequest::send(const QJSValue &data) |
3842 | -{ |
3843 | - QByteArray byteData; |
3844 | - |
3845 | - if (data.isUndefined() || data.isNull()) { |
3846 | - // Do nothing, byteData is empty. |
3847 | - } else if (data.isString()) { |
3848 | - byteData = data.toString().toUtf8(); |
3849 | - } else if (data.isObject()) { |
3850 | - if (data.hasProperty("byteLength")) { |
3851 | - // Looks like an ArrayView or an ArrayBufferView! |
3852 | - QJSValue buffer = data.property("buffer"); |
3853 | - if (buffer.isUndefined()) { |
3854 | - // We must assume we've been passed an ArrayBuffer directly |
3855 | - buffer = data; |
3856 | - } |
3857 | - |
3858 | - QJSValue array = data.property("_bytes"); |
3859 | - int byteLength = data.property("byteLength").toInt(); |
3860 | - |
3861 | - if (array.isArray()) { |
3862 | - byteData.reserve(byteLength); |
3863 | - |
3864 | - for (int i = 0; i < byteLength; i++) { |
3865 | - byteData.append(array.property(i).toInt()); |
3866 | - } |
3867 | - |
3868 | - qCDebug(l) << "passed an ArrayBufferView of" << byteData.length() << "bytes"; |
3869 | - } else { |
3870 | - qCWarning(l) << "passed an unknown/invalid ArrayBuffer" << data.toString(); |
3871 | - } |
3872 | - } else { |
3873 | - qCWarning(l) << "passed an unknown object" << data.toString(); |
3874 | - } |
3875 | - |
3876 | - } |
3877 | - |
3878 | - QBuffer *buffer; |
3879 | - if (!byteData.isEmpty()) { |
3880 | - buffer = new QBuffer; |
3881 | - buffer->setData(byteData); |
3882 | - } else { |
3883 | - buffer = 0; |
3884 | - } |
3885 | - |
3886 | - qCDebug(l) << "sending" << _verb << "to" << _request.url() << "with" << QString::fromUtf8(byteData); |
3887 | - _reply = _net->sendCustomRequest(_request, _verb.toLatin1(), buffer); |
3888 | - |
3889 | - connect(_reply, &QNetworkReply::finished, |
3890 | - this, &JSKitXMLHttpRequest::handleReplyFinished); |
3891 | - connect(_reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), |
3892 | - this, &JSKitXMLHttpRequest::handleReplyError); |
3893 | - |
3894 | - if (buffer) { |
3895 | - // So that it gets deleted alongside the reply object. |
3896 | - buffer->setParent(_reply); |
3897 | - } |
3898 | -} |
3899 | - |
3900 | -void JSKitXMLHttpRequest::abort() |
3901 | -{ |
3902 | - if (_reply) { |
3903 | - _reply->deleteLater(); |
3904 | - _reply = 0; |
3905 | - } |
3906 | -} |
3907 | - |
3908 | -QJSValue JSKitXMLHttpRequest::onload() const |
3909 | -{ |
3910 | - return _onload; |
3911 | -} |
3912 | - |
3913 | -void JSKitXMLHttpRequest::setOnload(const QJSValue &value) |
3914 | -{ |
3915 | - _onload = value; |
3916 | -} |
3917 | - |
3918 | -QJSValue JSKitXMLHttpRequest::ontimeout() const |
3919 | -{ |
3920 | - return _ontimeout; |
3921 | -} |
3922 | - |
3923 | -void JSKitXMLHttpRequest::setOntimeout(const QJSValue &value) |
3924 | -{ |
3925 | - _ontimeout = value; |
3926 | -} |
3927 | - |
3928 | -QJSValue JSKitXMLHttpRequest::onerror() const |
3929 | -{ |
3930 | - return _onerror; |
3931 | -} |
3932 | - |
3933 | -void JSKitXMLHttpRequest::setOnerror(const QJSValue &value) |
3934 | -{ |
3935 | - _onerror = value; |
3936 | -} |
3937 | - |
3938 | -uint JSKitXMLHttpRequest::readyState() const |
3939 | -{ |
3940 | - if (!_reply) { |
3941 | - return UNSENT; |
3942 | - } else if (_reply->isFinished()) { |
3943 | - return DONE; |
3944 | - } else { |
3945 | - return LOADING; |
3946 | - } |
3947 | -} |
3948 | - |
3949 | -uint JSKitXMLHttpRequest::timeout() const |
3950 | -{ |
3951 | - return _timeout; |
3952 | -} |
3953 | - |
3954 | -void JSKitXMLHttpRequest::setTimeout(uint value) |
3955 | -{ |
3956 | - _timeout = value; |
3957 | - // TODO Handle fetch in-progress. |
3958 | -} |
3959 | - |
3960 | -uint JSKitXMLHttpRequest::status() const |
3961 | -{ |
3962 | - if (!_reply || !_reply->isFinished()) { |
3963 | - return 0; |
3964 | - } else { |
3965 | - return _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt(); |
3966 | - } |
3967 | -} |
3968 | - |
3969 | -QString JSKitXMLHttpRequest::statusText() const |
3970 | -{ |
3971 | - if (!_reply || !_reply->isFinished()) { |
3972 | - return QString(); |
3973 | - } else { |
3974 | - return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); |
3975 | - } |
3976 | -} |
3977 | - |
3978 | -QString JSKitXMLHttpRequest::responseType() const |
3979 | -{ |
3980 | - return _responseType; |
3981 | -} |
3982 | - |
3983 | -void JSKitXMLHttpRequest::setResponseType(const QString &type) |
3984 | -{ |
3985 | - qCDebug(l) << "response type set to" << type; |
3986 | - _responseType = type; |
3987 | -} |
3988 | - |
3989 | -QJSValue JSKitXMLHttpRequest::response() const |
3990 | -{ |
3991 | - QJSEngine *engine = _mgr->engine(); |
3992 | - if (_responseType.isEmpty() || _responseType == "text") { |
3993 | - return engine->toScriptValue(QString::fromUtf8(_response)); |
3994 | - } else if (_responseType == "arraybuffer") { |
3995 | - QJSValue arrayBufferProto = engine->globalObject().property("ArrayBuffer").property("prototype"); |
3996 | - QJSValue arrayBuf = engine->newObject(); |
3997 | - if (!arrayBufferProto.isUndefined()) { |
3998 | - arrayBuf.setPrototype(arrayBufferProto); |
3999 | - arrayBuf.setProperty("byteLength", engine->toScriptValue<uint>(_response.size())); |
4000 | - QJSValue array = engine->newArray(_response.size()); |
4001 | - for (int i = 0; i < _response.size(); i++) { |
4002 | - array.setProperty(i, engine->toScriptValue<int>(_response[i])); |
4003 | - } |
4004 | - arrayBuf.setProperty("_bytes", array); |
4005 | - qCDebug(l) << "returning ArrayBuffer of" << _response.size() << "bytes"; |
4006 | - } else { |
4007 | - qCWarning(l) << "Cannot find proto of ArrayBuffer"; |
4008 | - } |
4009 | - return arrayBuf; |
4010 | - } else { |
4011 | - qCWarning(l) << "unsupported responseType:" << _responseType; |
4012 | - return engine->toScriptValue<void*>(0); |
4013 | - } |
4014 | -} |
4015 | - |
4016 | -QString JSKitXMLHttpRequest::responseText() const |
4017 | -{ |
4018 | - return QString::fromUtf8(_response); |
4019 | -} |
4020 | - |
4021 | -void JSKitXMLHttpRequest::handleReplyFinished() |
4022 | -{ |
4023 | - if (!_reply) { |
4024 | - qCDebug(l) << "reply finished too late"; |
4025 | - return; |
4026 | - } |
4027 | - |
4028 | - _response = _reply->readAll(); |
4029 | - qCDebug(l) << "reply finished, reply text:" << QString::fromUtf8(_response); |
4030 | - |
4031 | - emit readyStateChanged(); |
4032 | - emit statusChanged(); |
4033 | - emit statusTextChanged(); |
4034 | - emit responseChanged(); |
4035 | - emit responseTextChanged(); |
4036 | - |
4037 | - if (_onload.isCallable()) { |
4038 | - qCDebug(l) << "going to call onload handler:" << _onload.toString(); |
4039 | - QJSValue result = _onload.callWithInstance(_mgr->engine()->newQObject(this)); |
4040 | - if (result.isError()) { |
4041 | - qCWarning(l) << "JS error on onload handler:" << JSKitManager::describeError(result); |
4042 | - } |
4043 | - } else { |
4044 | - qCDebug(l) << "No onload set"; |
4045 | - } |
4046 | -} |
4047 | - |
4048 | -void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) |
4049 | -{ |
4050 | - if (!_reply) { |
4051 | - qCDebug(l) << "reply error too late"; |
4052 | - return; |
4053 | - } |
4054 | - |
4055 | - qCDebug(l) << "reply error" << code; |
4056 | - |
4057 | - emit readyStateChanged(); |
4058 | - emit statusChanged(); |
4059 | - emit statusTextChanged(); |
4060 | - |
4061 | - if (_onerror.isCallable()) { |
4062 | - qCDebug(l) << "going to call onerror handler:" << _onload.toString(); |
4063 | - QJSValue result = _onerror.callWithInstance(_mgr->engine()->newQObject(this)); |
4064 | - if (result.isError()) { |
4065 | - qCWarning(l) << "JS error on onerror handler:" << JSKitManager::describeError(result); |
4066 | - } |
4067 | - } |
4068 | -} |
4069 | - |
4070 | -void JSKitXMLHttpRequest::handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth) |
4071 | -{ |
4072 | - if (_reply == reply) { |
4073 | - qCDebug(l) << "authentication required"; |
4074 | - |
4075 | - if (!_username.isEmpty() || !_password.isEmpty()) { |
4076 | - qCDebug(l) << "using provided authorization:" << _username; |
4077 | - |
4078 | - auth->setUser(_username); |
4079 | - auth->setPassword(_password); |
4080 | - } else { |
4081 | - qCDebug(l) << "no username or password provided"; |
4082 | - } |
4083 | - } |
4084 | -} |
4085 | - |
4086 | -JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr, QObject *parent) |
4087 | - : QObject(parent), l(metaObject()->className()), |
4088 | - _mgr(mgr), _source(0), _lastWatchId(0) |
4089 | -{ |
4090 | -} |
4091 | - |
4092 | -void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) |
4093 | -{ |
4094 | - setupWatcher(successCallback, errorCallback, options, true); |
4095 | -} |
4096 | - |
4097 | -int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) |
4098 | -{ |
4099 | - return setupWatcher(successCallback, errorCallback, options, false); |
4100 | -} |
4101 | - |
4102 | -void JSKitGeolocation::clearWatch(int watchId) |
4103 | -{ |
4104 | - removeWatcher(watchId); |
4105 | -} |
4106 | - |
4107 | -void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) |
4108 | -{ |
4109 | - qCWarning(l) << "positioning error: " << error; |
4110 | - // TODO |
4111 | -} |
4112 | - |
4113 | -void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) |
4114 | -{ |
4115 | - qCDebug(l) << "got position at" << pos.timestamp() << "type" << pos.coordinate().type(); |
4116 | - |
4117 | - if (_watches.empty()) { |
4118 | - qCWarning(l) << "got position update but no one is watching"; |
4119 | - _source->stopUpdates(); // Just in case. |
4120 | - return; |
4121 | - } |
4122 | - |
4123 | - QJSValue obj = buildPositionObject(pos); |
4124 | - |
4125 | - for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { |
4126 | - invokeCallback(it->successCallback, obj); |
4127 | - |
4128 | - if (it->once) { |
4129 | - it = _watches.erase(it); |
4130 | - } else { |
4131 | - it->timer.restart(); |
4132 | - ++it; |
4133 | - } |
4134 | - } |
4135 | -} |
4136 | - |
4137 | -void JSKitGeolocation::handleTimeout() |
4138 | -{ |
4139 | - qCDebug(l) << "positioning timeout"; |
4140 | - |
4141 | - if (_watches.empty()) { |
4142 | - qCWarning(l) << "got position timeout but no one is watching"; |
4143 | - _source->stopUpdates(); |
4144 | - return; |
4145 | - } |
4146 | - |
4147 | - QJSValue obj = buildPositionErrorObject(TIMEOUT, "timeout"); |
4148 | - |
4149 | - for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { |
4150 | - if (it->timer.hasExpired(it->timeout)) { |
4151 | - qCDebug(l) << "positioning timeout for watch" << it->watchId |
4152 | - << ", watch is" << it->timer.elapsed() << "ms old, timeout is" << it->timeout; |
4153 | - invokeCallback(it->errorCallback, obj); |
4154 | - |
4155 | - if (it->once) { |
4156 | - it = _watches.erase(it); |
4157 | - } else { |
4158 | - it->timer.restart(); |
4159 | - ++it; |
4160 | - } |
4161 | - } else { |
4162 | - ++it; |
4163 | - } |
4164 | - } |
4165 | - |
4166 | - QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection); |
4167 | -} |
4168 | - |
4169 | -void JSKitGeolocation::updateTimeouts() |
4170 | -{ |
4171 | - int once_timeout = -1, updates_timeout = -1; |
4172 | - |
4173 | - qCDebug(l) << Q_FUNC_INFO; |
4174 | - |
4175 | - Q_FOREACH(const Watcher &watcher, _watches) { |
4176 | - qint64 rem_timeout = watcher.timeout - watcher.timer.elapsed(); |
4177 | - qCDebug(l) << "watch" << watcher.watchId << "rem timeout" << rem_timeout; |
4178 | - if (rem_timeout >= 0) { |
4179 | - // In case it is too large... |
4180 | - rem_timeout = qMin<qint64>(rem_timeout, std::numeric_limits<int>::max()); |
4181 | - if (watcher.once) { |
4182 | - once_timeout = once_timeout >= 0 ? qMin<int>(once_timeout, rem_timeout) : rem_timeout; |
4183 | - } else { |
4184 | - updates_timeout = updates_timeout >= 0 ? qMin<int>(updates_timeout, rem_timeout) : rem_timeout; |
4185 | - } |
4186 | - } |
4187 | - } |
4188 | - |
4189 | - if (updates_timeout >= 0) { |
4190 | - qCDebug(l) << "setting location update interval to" << updates_timeout; |
4191 | - _source->setUpdateInterval(updates_timeout); |
4192 | - _source->startUpdates(); |
4193 | - } else { |
4194 | - qCDebug(l) << "stopping updates"; |
4195 | - _source->stopUpdates(); |
4196 | - } |
4197 | - |
4198 | - if (once_timeout >= 0) { |
4199 | - qCDebug(l) << "requesting single location update with timeout" << once_timeout; |
4200 | - _source->requestUpdate(once_timeout); |
4201 | - } |
4202 | -} |
4203 | - |
4204 | -int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once) |
4205 | -{ |
4206 | - Watcher watcher; |
4207 | - watcher.successCallback = successCallback; |
4208 | - watcher.errorCallback = errorCallback; |
4209 | - watcher.highAccuracy = options.value("enableHighAccuracy").toBool(); |
4210 | - watcher.timeout = options.value("timeout", std::numeric_limits<int>::max() - 1).toInt(); |
4211 | - watcher.once = once; |
4212 | - watcher.watchId = ++_lastWatchId; |
4213 | - |
4214 | - qlonglong maximumAge = options.value("maximumAge", 0).toLongLong(); |
4215 | - |
4216 | - qCDebug(l) << "setting up watcher, gps=" << watcher.highAccuracy << "timeout=" << watcher.timeout << "maximumAge=" << maximumAge << "once=" << once; |
4217 | - |
4218 | - if (!_source) { |
4219 | - _source = QGeoPositionInfoSource::createDefaultSource(this); |
4220 | - connect(_source, static_cast<void (QGeoPositionInfoSource::*)(QGeoPositionInfoSource::Error)>(&QGeoPositionInfoSource::error), |
4221 | - this, &JSKitGeolocation::handleError); |
4222 | - connect(_source, &QGeoPositionInfoSource::positionUpdated, |
4223 | - this, &JSKitGeolocation::handlePosition); |
4224 | - connect(_source, &QGeoPositionInfoSource::updateTimeout, |
4225 | - this, &JSKitGeolocation::handleTimeout); |
4226 | - } |
4227 | - |
4228 | - if (maximumAge > 0) { |
4229 | - QDateTime threshold = QDateTime::currentDateTime().addMSecs(-qint64(maximumAge)); |
4230 | - QGeoPositionInfo pos = _source->lastKnownPosition(watcher.highAccuracy); |
4231 | - qCDebug(l) << "got pos timestamp" << pos.timestamp() << " but we want" << threshold; |
4232 | - if (pos.isValid() && pos.timestamp() >= threshold) { |
4233 | - invokeCallback(watcher.successCallback, buildPositionObject(pos)); |
4234 | - if (once) { |
4235 | - return -1; |
4236 | - } |
4237 | - } else if (watcher.timeout == 0 && once) { |
4238 | - // If the timeout has already expired, and we have no cached data |
4239 | - // Do not even bother to turn on the GPS; return error object now. |
4240 | - invokeCallback(watcher.errorCallback, buildPositionErrorObject(TIMEOUT, "no cached position")); |
4241 | - return -1; |
4242 | - } |
4243 | - } |
4244 | - |
4245 | - watcher.timer.start(); |
4246 | - _watches.append(watcher); |
4247 | - |
4248 | - qCDebug(l) << "added new watch" << watcher.watchId; |
4249 | - |
4250 | - QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection); |
4251 | - |
4252 | - return watcher.watchId; |
4253 | -} |
4254 | - |
4255 | -void JSKitGeolocation::removeWatcher(int watchId) |
4256 | -{ |
4257 | - Watcher watcher; |
4258 | - |
4259 | - qCDebug(l) << "removing watchId" << watcher.watchId; |
4260 | - |
4261 | - for (int i = 0; i < _watches.size(); i++) { |
4262 | - if (_watches[i].watchId == watchId) { |
4263 | - watcher = _watches.takeAt(i); |
4264 | - break; |
4265 | - } |
4266 | - } |
4267 | - |
4268 | - if (watcher.watchId != watchId) { |
4269 | - qCWarning(l) << "watchId not found"; |
4270 | - return; |
4271 | - } |
4272 | - |
4273 | - QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection); |
4274 | -} |
4275 | - |
4276 | -QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos) |
4277 | -{ |
4278 | - QJSEngine *engine = _mgr->engine(); |
4279 | - QJSValue obj = engine->newObject(); |
4280 | - QJSValue coords = engine->newObject(); |
4281 | - QJSValue timestamp = engine->toScriptValue<quint64>(pos.timestamp().toMSecsSinceEpoch()); |
4282 | - |
4283 | - coords.setProperty("latitude", engine->toScriptValue(pos.coordinate().latitude())); |
4284 | - coords.setProperty("longitude", engine->toScriptValue(pos.coordinate().longitude())); |
4285 | - if (pos.coordinate().type() == QGeoCoordinate::Coordinate3D) { |
4286 | - coords.setProperty("altitude", engine->toScriptValue(pos.coordinate().altitude())); |
4287 | - } else { |
4288 | - coords.setProperty("altitude", engine->toScriptValue<void*>(0)); |
4289 | - } |
4290 | - |
4291 | - coords.setProperty("accuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::HorizontalAccuracy))); |
4292 | - |
4293 | - if (pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) { |
4294 | - coords.setProperty("altitudeAccuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::VerticalAccuracy))); |
4295 | - } else { |
4296 | - coords.setProperty("altitudeAccuracy", engine->toScriptValue<void*>(0)); |
4297 | - } |
4298 | - |
4299 | - if (pos.hasAttribute(QGeoPositionInfo::Direction)) { |
4300 | - coords.setProperty("heading", engine->toScriptValue(pos.attribute(QGeoPositionInfo::Direction))); |
4301 | - } else { |
4302 | - coords.setProperty("heading", engine->toScriptValue<void*>(0)); |
4303 | - } |
4304 | - |
4305 | - if (pos.hasAttribute(QGeoPositionInfo::GroundSpeed)) { |
4306 | - coords.setProperty("speed", engine->toScriptValue(pos.attribute(QGeoPositionInfo::GroundSpeed))); |
4307 | - } else { |
4308 | - coords.setProperty("speed", engine->toScriptValue<void*>(0)); |
4309 | - } |
4310 | - |
4311 | - obj.setProperty("coords", coords); |
4312 | - obj.setProperty("timestamp", timestamp); |
4313 | - |
4314 | - return obj; |
4315 | -} |
4316 | - |
4317 | -QJSValue JSKitGeolocation::buildPositionErrorObject(PositionError error, const QString &message) |
4318 | -{ |
4319 | - QJSEngine *engine = _mgr->engine(); |
4320 | - QJSValue obj = engine->newObject(); |
4321 | - |
4322 | - obj.setProperty("code", engine->toScriptValue<unsigned short>(error)); |
4323 | - obj.setProperty("message", engine->toScriptValue(message)); |
4324 | - |
4325 | - return obj; |
4326 | -} |
4327 | - |
4328 | -void JSKitGeolocation::invokeCallback(QJSValue callback, QJSValue event) |
4329 | -{ |
4330 | - if (callback.isCallable()) { |
4331 | - qCDebug(l) << "invoking callback" << callback.toString(); |
4332 | - QJSValue result = callback.call(QJSValueList({event})); |
4333 | - if (result.isError()) { |
4334 | - qCWarning(l) << "while invoking callback: " << JSKitManager::describeError(result); |
4335 | - } |
4336 | - } else { |
4337 | - qCWarning(l) << "callback is not callable"; |
4338 | - } |
4339 | -} |
4340 | |
4341 | === removed file 'rockworkd/libpebble/jskitobjects.h' |
4342 | --- rockworkd/libpebble/jskitobjects.h 2016-01-10 22:35:04 +0000 |
4343 | +++ rockworkd/libpebble/jskitobjects.h 1970-01-01 00:00:00 +0000 |
4344 | @@ -1,235 +0,0 @@ |
4345 | -#ifndef JSKITMANAGER_P_H |
4346 | -#define JSKITMANAGER_P_H |
4347 | - |
4348 | -#include <QElapsedTimer> |
4349 | -#include <QSettings> |
4350 | -#include <QNetworkRequest> |
4351 | -#include <QNetworkReply> |
4352 | -#include <QGeoPositionInfoSource> |
4353 | -#include "jskitmanager.h" |
4354 | -#include "appinfo.h" |
4355 | - |
4356 | -class JSKitPebble : public QObject |
4357 | -{ |
4358 | - Q_OBJECT |
4359 | - |
4360 | -public: |
4361 | - explicit JSKitPebble(const AppInfo &appInfo, JSKitManager *mgr, QObject *parent=0); |
4362 | - |
4363 | - Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); |
4364 | - Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); |
4365 | - |
4366 | - Q_INVOKABLE int setInterval(QJSValue expression, int delay); |
4367 | - Q_INVOKABLE void clearInterval(int timerId); |
4368 | - |
4369 | - Q_INVOKABLE int setTimeout(QJSValue expression, int delay); |
4370 | - Q_INVOKABLE void clearTimeout(int timerId); |
4371 | - |
4372 | - Q_INVOKABLE uint sendAppMessage(QJSValue message, QJSValue callbackForAck = QJSValue(), QJSValue callbackForNack = QJSValue()); |
4373 | - |
4374 | - Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body); |
4375 | - |
4376 | - Q_INVOKABLE void openURL(const QUrl &url); |
4377 | - |
4378 | - Q_INVOKABLE QString getAccountToken() const; |
4379 | - Q_INVOKABLE QString getWatchToken() const; |
4380 | - |
4381 | - Q_INVOKABLE QJSValue createXMLHttpRequest(); |
4382 | - |
4383 | - void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); |
4384 | - |
4385 | -protected: |
4386 | - void timerEvent(QTimerEvent *event); |
4387 | - |
4388 | -private: |
4389 | - QJSValue buildAckEventObject(uint transaction, const QString &message = QString()) const; |
4390 | - |
4391 | -private: |
4392 | - AppInfo _appInfo; |
4393 | - JSKitManager *_mgr; |
4394 | - QHash<QString, QList<QJSValue>> _callbacks; |
4395 | - QHash<int, QJSValue> _intervals; |
4396 | - QHash<int, QJSValue> _timeouts; |
4397 | -}; |
4398 | - |
4399 | -class JSKitConsole : public QObject |
4400 | -{ |
4401 | - Q_OBJECT |
4402 | - QLoggingCategory l; |
4403 | - |
4404 | -public: |
4405 | - explicit JSKitConsole(QObject *parent=0); |
4406 | - |
4407 | - Q_INVOKABLE void log(const QString &msg); |
4408 | - Q_INVOKABLE void warn(const QString &msg); |
4409 | - Q_INVOKABLE void error(const QString &msg); |
4410 | - Q_INVOKABLE void info(const QString &msg); |
4411 | -}; |
4412 | - |
4413 | -class JSKitLocalStorage : public QObject |
4414 | -{ |
4415 | - Q_OBJECT |
4416 | - |
4417 | - Q_PROPERTY(int length READ length NOTIFY lengthChanged) |
4418 | - |
4419 | -public: |
4420 | - explicit JSKitLocalStorage(const QString &storagePath, const QUuid &uuid, QObject *parent=0); |
4421 | - |
4422 | - int length() const; |
4423 | - |
4424 | - Q_INVOKABLE QJSValue getItem(const QString &key) const; |
4425 | - Q_INVOKABLE void setItem(const QString &key, const QString &value); |
4426 | - Q_INVOKABLE void removeItem(const QString &key); |
4427 | - |
4428 | - Q_INVOKABLE void clear(); |
4429 | - |
4430 | -signals: |
4431 | - void lengthChanged(); |
4432 | - |
4433 | -private: |
4434 | - void checkLengthChanged(); |
4435 | - static QString getStorageFileFor(const QString &storageDir, const QUuid &uuid); |
4436 | - |
4437 | -private: |
4438 | - QSettings *_storage; |
4439 | - int _len; |
4440 | -}; |
4441 | - |
4442 | -class JSKitXMLHttpRequest : public QObject |
4443 | -{ |
4444 | - Q_OBJECT |
4445 | - QLoggingCategory l; |
4446 | - Q_ENUMS(ReadyStates) |
4447 | - |
4448 | - Q_PROPERTY(QJSValue onload READ onload WRITE setOnload) |
4449 | - Q_PROPERTY(QJSValue ontimeout READ ontimeout WRITE setOntimeout) |
4450 | - Q_PROPERTY(QJSValue onerror READ onerror WRITE setOnerror) |
4451 | - Q_PROPERTY(uint readyState READ readyState NOTIFY readyStateChanged) |
4452 | - Q_PROPERTY(uint timeout READ timeout WRITE setTimeout) |
4453 | - Q_PROPERTY(uint status READ status NOTIFY statusChanged) |
4454 | - Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged) |
4455 | - Q_PROPERTY(QString responseType READ responseType WRITE setResponseType) |
4456 | - Q_PROPERTY(QJSValue response READ response NOTIFY responseChanged) |
4457 | - Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged) |
4458 | - |
4459 | -public: |
4460 | - explicit JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent = 0); |
4461 | - ~JSKitXMLHttpRequest(); |
4462 | - |
4463 | - enum ReadyStates { |
4464 | - UNSENT = 0, |
4465 | - OPENED = 1, |
4466 | - HEADERS_RECEIVED = 2, |
4467 | - LOADING = 3, |
4468 | - DONE = 4 |
4469 | - }; |
4470 | - |
4471 | - Q_INVOKABLE void open(const QString &method, const QString &url, bool async = false, const QString &username = QString(), const QString &password = QString()); |
4472 | - Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value); |
4473 | - Q_INVOKABLE void send(const QJSValue &data = QJSValue(QJSValue::NullValue)); |
4474 | - Q_INVOKABLE void abort(); |
4475 | - |
4476 | - QJSValue onload() const; |
4477 | - void setOnload(const QJSValue &value); |
4478 | - QJSValue ontimeout() const; |
4479 | - void setOntimeout(const QJSValue &value); |
4480 | - QJSValue onerror() const; |
4481 | - void setOnerror(const QJSValue &value); |
4482 | - |
4483 | - uint readyState() const; |
4484 | - |
4485 | - uint timeout() const; |
4486 | - void setTimeout(uint value); |
4487 | - |
4488 | - uint status() const; |
4489 | - QString statusText() const; |
4490 | - |
4491 | - QString responseType() const; |
4492 | - void setResponseType(const QString& type); |
4493 | - |
4494 | - QJSValue response() const; |
4495 | - QString responseText() const; |
4496 | - |
4497 | -signals: |
4498 | - void readyStateChanged(); |
4499 | - void statusChanged(); |
4500 | - void statusTextChanged(); |
4501 | - void responseChanged(); |
4502 | - void responseTextChanged(); |
4503 | - |
4504 | -private slots: |
4505 | - void handleReplyFinished(); |
4506 | - void handleReplyError(QNetworkReply::NetworkError code); |
4507 | - void handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth); |
4508 | - |
4509 | -private: |
4510 | - JSKitManager *_mgr; |
4511 | - QNetworkAccessManager *_net; |
4512 | - QString _verb; |
4513 | - uint _timeout; |
4514 | - QString _username; |
4515 | - QString _password; |
4516 | - QNetworkRequest _request; |
4517 | - QNetworkReply *_reply; |
4518 | - QString _responseType; |
4519 | - QByteArray _response; |
4520 | - QJSValue _onload; |
4521 | - QJSValue _ontimeout; |
4522 | - QJSValue _onerror; |
4523 | -}; |
4524 | - |
4525 | -class JSKitGeolocation : public QObject |
4526 | -{ |
4527 | - Q_OBJECT |
4528 | - Q_ENUMS(PositionError) |
4529 | - QLoggingCategory l; |
4530 | - |
4531 | - struct Watcher; |
4532 | - |
4533 | -public: |
4534 | - explicit JSKitGeolocation(JSKitManager *mgr, QObject *parent=0); |
4535 | - |
4536 | - enum PositionError { |
4537 | - PERMISSION_DENIED = 1, |
4538 | - POSITION_UNAVAILABLE = 2, |
4539 | - TIMEOUT = 3 |
4540 | - }; |
4541 | - |
4542 | - Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); |
4543 | - Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); |
4544 | - Q_INVOKABLE void clearWatch(int watchId); |
4545 | - |
4546 | -private slots: |
4547 | - void handleError(const QGeoPositionInfoSource::Error error); |
4548 | - void handlePosition(const QGeoPositionInfo &pos); |
4549 | - void handleTimeout(); |
4550 | - void updateTimeouts(); |
4551 | - |
4552 | -private: |
4553 | - int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once); |
4554 | - void removeWatcher(int watchId); |
4555 | - |
4556 | - QJSValue buildPositionObject(const QGeoPositionInfo &pos); |
4557 | - QJSValue buildPositionErrorObject(PositionError error, const QString &message = QString()); |
4558 | - QJSValue buildPositionErrorObject(const QGeoPositionInfoSource::Error error); |
4559 | - void invokeCallback(QJSValue callback, QJSValue event); |
4560 | - |
4561 | -private: |
4562 | - JSKitManager *_mgr; |
4563 | - QGeoPositionInfoSource *_source; |
4564 | - |
4565 | - struct Watcher { |
4566 | - QJSValue successCallback; |
4567 | - QJSValue errorCallback; |
4568 | - int watchId; |
4569 | - bool once; |
4570 | - bool highAccuracy; |
4571 | - int timeout; |
4572 | - QElapsedTimer timer; |
4573 | - }; |
4574 | - |
4575 | - QList<Watcher> _watches; |
4576 | - int _lastWatchId; |
4577 | -}; |
4578 | - |
4579 | -#endif // JSKITMANAGER_P_H |
4580 | |
4581 | === modified file 'rockworkd/libpebble/pebble.cpp' |
4582 | --- rockworkd/libpebble/pebble.cpp 2016-02-01 23:58:55 +0000 |
4583 | +++ rockworkd/libpebble/pebble.cpp 2016-02-06 14:32:32 +0000 |
4584 | @@ -7,7 +7,7 @@ |
4585 | #include "phonecallendpoint.h" |
4586 | #include "appmanager.h" |
4587 | #include "appmsgmanager.h" |
4588 | -#include "jskitmanager.h" |
4589 | +#include "jskit/jskitmanager.h" |
4590 | #include "blobdb.h" |
4591 | #include "appdownloader.h" |
4592 | #include "screenshotendpoint.h" |
4593 | @@ -166,6 +166,11 @@ |
4594 | return m_serialNumber; |
4595 | } |
4596 | |
4597 | +QString Pebble::language() const |
4598 | +{ |
4599 | + return m_language; |
4600 | +} |
4601 | + |
4602 | Capabilities Pebble::capabilities() const |
4603 | { |
4604 | return m_capabilities; |
4605 | @@ -409,7 +414,8 @@ |
4606 | qDebug() << "BT address" << wd.readBytes(6).toHex(); |
4607 | qDebug() << "CRC:" << wd.read<quint32>(); |
4608 | qDebug() << "Resource timestamp:" << QDateTime::fromTime_t(wd.read<quint32>()); |
4609 | - qDebug() << "Language" << wd.readFixedString(6); |
4610 | + m_language = wd.readFixedString(6); |
4611 | + qDebug() << "Language" << m_language; |
4612 | qDebug() << "Language version" << wd.read<quint16>(); |
4613 | // Capabilities is 64 bits but QFlags can only do 32 bits. lets split it into 2 * 32. |
4614 | // only 8 bits are used atm anyways. |
4615 | |
4616 | === modified file 'rockworkd/libpebble/pebble.h' |
4617 | --- rockworkd/libpebble/pebble.h 2016-02-01 23:58:55 +0000 |
4618 | +++ rockworkd/libpebble/pebble.h 2016-02-06 14:32:32 +0000 |
4619 | @@ -35,6 +35,7 @@ |
4620 | Q_PROPERTY(HardwarePlatform hardwarePlatform MEMBER m_hardwarePlatform) |
4621 | Q_PROPERTY(QString softwareVersion MEMBER m_softwareVersion) |
4622 | Q_PROPERTY(QString serialNumber MEMBER m_serialNumber) |
4623 | + Q_PROPERTY(QString language MEMBER m_language) |
4624 | |
4625 | public: |
4626 | explicit Pebble(const QBluetoothAddress &address, QObject *parent = 0); |
4627 | @@ -56,6 +57,7 @@ |
4628 | Model model() const; |
4629 | HardwarePlatform hardwarePlatform() const; |
4630 | QString serialNumber() const; |
4631 | + QString language() const; |
4632 | Capabilities capabilities() const; |
4633 | bool isUnfaithful() const; |
4634 | bool recovery() const; |
4635 | @@ -143,6 +145,7 @@ |
4636 | HardwarePlatform m_hardwarePlatform = HardwarePlatformUnknown; |
4637 | Model m_model = ModelUnknown; |
4638 | QString m_serialNumber; |
4639 | + QString m_language; |
4640 | Capabilities m_capabilities = CapabilityNone; |
4641 | bool m_isUnfaithful = false; |
4642 | bool m_recovery = false; |
4643 | |
4644 | === removed file 'rockworkd/libpebble/typedarray.js' |
4645 | --- rockworkd/libpebble/typedarray.js 2016-01-03 15:22:24 +0000 |
4646 | +++ rockworkd/libpebble/typedarray.js 1970-01-01 00:00:00 +0000 |
4647 | @@ -1,1030 +0,0 @@ |
4648 | -/* |
4649 | - Copyright (c) 2010, Linden Research, Inc. |
4650 | - Copyright (c) 2014, Joshua Bell |
4651 | - |
4652 | - Permission is hereby granted, free of charge, to any person obtaining a copy |
4653 | - of this software and associated documentation files (the "Software"), to deal |
4654 | - in the Software without restriction, including without limitation the rights |
4655 | - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
4656 | - copies of the Software, and to permit persons to whom the Software is |
4657 | - furnished to do so, subject to the following conditions: |
4658 | - |
4659 | - The above copyright notice and this permission notice shall be included in |
4660 | - all copies or substantial portions of the Software. |
4661 | - |
4662 | - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
4663 | - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
4664 | - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
4665 | - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
4666 | - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
4667 | - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
4668 | - THE SOFTWARE. |
4669 | - $/LicenseInfo$ |
4670 | - */ |
4671 | - |
4672 | -// Original can be found at: |
4673 | -// https://bitbucket.org/lindenlab/llsd |
4674 | -// Modifications by Joshua Bell inexorabletash@gmail.com |
4675 | -// https://github.com/inexorabletash/polyfill |
4676 | - |
4677 | -// ES3/ES5 implementation of the Krhonos Typed Array Specification |
4678 | -// Ref: http://www.khronos.org/registry/typedarray/specs/latest/ |
4679 | -// Date: 2011-02-01 |
4680 | -// |
4681 | -// Variations: |
4682 | -// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) |
4683 | -// * Gradually migrating structure from Khronos spec to ES6 spec |
4684 | -(function(global) { |
4685 | - 'use strict'; |
4686 | - var undefined = (void 0); // Paranoia |
4687 | - |
4688 | - // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to |
4689 | - // create, and consume so much memory, that the browser appears frozen. |
4690 | - var MAX_ARRAY_LENGTH = 1e5; |
4691 | - |
4692 | - // Approximations of internal ECMAScript conversion functions |
4693 | - function Type(v) { |
4694 | - switch(typeof v) { |
4695 | - case 'undefined': return 'undefined'; |
4696 | - case 'boolean': return 'boolean'; |
4697 | - case 'number': return 'number'; |
4698 | - case 'string': return 'string'; |
4699 | - default: return v === null ? 'null' : 'object'; |
4700 | - } |
4701 | - } |
4702 | - |
4703 | - // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues: |
4704 | - function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); } |
4705 | - function IsCallable(o) { return typeof o === 'function'; } |
4706 | - function ToObject(v) { |
4707 | - if (v === null || v === undefined) throw TypeError(); |
4708 | - return Object(v); |
4709 | - } |
4710 | - function ToInt32(v) { return v >> 0; } |
4711 | - function ToUint32(v) { return v >>> 0; } |
4712 | - |
4713 | - // Snapshot intrinsics |
4714 | - var LN2 = Math.LN2, |
4715 | - abs = Math.abs, |
4716 | - floor = Math.floor, |
4717 | - log = Math.log, |
4718 | - max = Math.max, |
4719 | - min = Math.min, |
4720 | - pow = Math.pow, |
4721 | - round = Math.round; |
4722 | - |
4723 | - // emulate ES5 getter/setter API using legacy APIs |
4724 | - // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx |
4725 | - // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but |
4726 | - // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless) |
4727 | - |
4728 | - (function() { |
4729 | - var orig = Object.defineProperty; |
4730 | - var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}()); |
4731 | - |
4732 | - if (!orig || dom_only) { |
4733 | - Object.defineProperty = function (o, prop, desc) { |
4734 | - // In IE8 try built-in implementation for defining properties on DOM prototypes. |
4735 | - if (orig) |
4736 | - try { return orig(o, prop, desc); } catch (_) {} |
4737 | - if (o !== Object(o)) |
4738 | - throw TypeError('Object.defineProperty called on non-object'); |
4739 | - if (Object.prototype.__defineGetter__ && ('get' in desc)) |
4740 | - Object.prototype.__defineGetter__.call(o, prop, desc.get); |
4741 | - if (Object.prototype.__defineSetter__ && ('set' in desc)) |
4742 | - Object.prototype.__defineSetter__.call(o, prop, desc.set); |
4743 | - if ('value' in desc) |
4744 | - o[prop] = desc.value; |
4745 | - return o; |
4746 | - }; |
4747 | - } |
4748 | - }()); |
4749 | - |
4750 | - // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value) |
4751 | - // for index in 0 ... obj.length |
4752 | - function makeArrayAccessors(obj) { |
4753 | - if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill'); |
4754 | - |
4755 | - function makeArrayAccessor(index) { |
4756 | - Object.defineProperty(obj, index, { |
4757 | - 'get': function() { return obj._getter(index); }, |
4758 | - 'set': function(v) { obj._setter(index, v); }, |
4759 | - enumerable: true, |
4760 | - configurable: false |
4761 | - }); |
4762 | - } |
4763 | - |
4764 | - var i; |
4765 | - for (i = 0; i < obj.length; i += 1) { |
4766 | - makeArrayAccessor(i); |
4767 | - } |
4768 | - } |
4769 | - |
4770 | - // Internal conversion functions: |
4771 | - // pack<Type>() - take a number (interpreted as Type), output a byte array |
4772 | - // unpack<Type>() - take a byte array, output a Type-like number |
4773 | - |
4774 | - function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; } |
4775 | - function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; } |
4776 | - |
4777 | - function packI8(n) { return [n & 0xff]; } |
4778 | - function unpackI8(bytes) { return as_signed(bytes[0], 8); } |
4779 | - |
4780 | - function packU8(n) { return [n & 0xff]; } |
4781 | - function unpackU8(bytes) { return as_unsigned(bytes[0], 8); } |
4782 | - |
4783 | - function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; } |
4784 | - |
4785 | - function packI16(n) { return [(n >> 8) & 0xff, n & 0xff]; } |
4786 | - function unpackI16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); } |
4787 | - |
4788 | - function packU16(n) { return [(n >> 8) & 0xff, n & 0xff]; } |
4789 | - function unpackU16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); } |
4790 | - |
4791 | - function packI32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } |
4792 | - function unpackI32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } |
4793 | - |
4794 | - function packU32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } |
4795 | - function unpackU32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } |
4796 | - |
4797 | - function packIEEE754(v, ebits, fbits) { |
4798 | - |
4799 | - var bias = (1 << (ebits - 1)) - 1, |
4800 | - s, e, f, ln, |
4801 | - i, bits, str, bytes; |
4802 | - |
4803 | - function roundToEven(n) { |
4804 | - var w = floor(n), f = n - w; |
4805 | - if (f < 0.5) |
4806 | - return w; |
4807 | - if (f > 0.5) |
4808 | - return w + 1; |
4809 | - return w % 2 ? w + 1 : w; |
4810 | - } |
4811 | - |
4812 | - // Compute sign, exponent, fraction |
4813 | - if (v !== v) { |
4814 | - // NaN |
4815 | - // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping |
4816 | - e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0; |
4817 | - } else if (v === Infinity || v === -Infinity) { |
4818 | - e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0; |
4819 | - } else if (v === 0) { |
4820 | - e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0; |
4821 | - } else { |
4822 | - s = v < 0; |
4823 | - v = abs(v); |
4824 | - |
4825 | - if (v >= pow(2, 1 - bias)) { |
4826 | - e = min(floor(log(v) / LN2), 1023); |
4827 | - f = roundToEven(v / pow(2, e) * pow(2, fbits)); |
4828 | - if (f / pow(2, fbits) >= 2) { |
4829 | - e = e + 1; |
4830 | - f = 1; |
4831 | - } |
4832 | - if (e > bias) { |
4833 | - // Overflow |
4834 | - e = (1 << ebits) - 1; |
4835 | - f = 0; |
4836 | - } else { |
4837 | - // Normalized |
4838 | - e = e + bias; |
4839 | - f = f - pow(2, fbits); |
4840 | - } |
4841 | - } else { |
4842 | - // Denormalized |
4843 | - e = 0; |
4844 | - f = roundToEven(v / pow(2, 1 - bias - fbits)); |
4845 | - } |
4846 | - } |
4847 | - |
4848 | - // Pack sign, exponent, fraction |
4849 | - bits = []; |
4850 | - for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); } |
4851 | - for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); } |
4852 | - bits.push(s ? 1 : 0); |
4853 | - bits.reverse(); |
4854 | - str = bits.join(''); |
4855 | - |
4856 | - // Bits to bytes |
4857 | - bytes = []; |
4858 | - while (str.length) { |
4859 | - bytes.push(parseInt(str.substring(0, 8), 2)); |
4860 | - str = str.substring(8); |
4861 | - } |
4862 | - return bytes; |
4863 | - } |
4864 | - |
4865 | - function unpackIEEE754(bytes, ebits, fbits) { |
4866 | - // Bytes to bits |
4867 | - var bits = [], i, j, b, str, |
4868 | - bias, s, e, f; |
4869 | - |
4870 | - for (i = bytes.length; i; i -= 1) { |
4871 | - b = bytes[i - 1]; |
4872 | - for (j = 8; j; j -= 1) { |
4873 | - bits.push(b % 2 ? 1 : 0); b = b >> 1; |
4874 | - } |
4875 | - } |
4876 | - bits.reverse(); |
4877 | - str = bits.join(''); |
4878 | - |
4879 | - // Unpack sign, exponent, fraction |
4880 | - bias = (1 << (ebits - 1)) - 1; |
4881 | - s = parseInt(str.substring(0, 1), 2) ? -1 : 1; |
4882 | - e = parseInt(str.substring(1, 1 + ebits), 2); |
4883 | - f = parseInt(str.substring(1 + ebits), 2); |
4884 | - |
4885 | - // Produce number |
4886 | - if (e === (1 << ebits) - 1) { |
4887 | - return f !== 0 ? NaN : s * Infinity; |
4888 | - } else if (e > 0) { |
4889 | - // Normalized |
4890 | - return s * pow(2, e - bias) * (1 + f / pow(2, fbits)); |
4891 | - } else if (f !== 0) { |
4892 | - // Denormalized |
4893 | - return s * pow(2, -(bias - 1)) * (f / pow(2, fbits)); |
4894 | - } else { |
4895 | - return s < 0 ? -0 : 0; |
4896 | - } |
4897 | - } |
4898 | - |
4899 | - function unpackF64(b) { return unpackIEEE754(b, 11, 52); } |
4900 | - function packF64(v) { return packIEEE754(v, 11, 52); } |
4901 | - function unpackF32(b) { return unpackIEEE754(b, 8, 23); } |
4902 | - function packF32(v) { return packIEEE754(v, 8, 23); } |
4903 | - |
4904 | - // |
4905 | - // 3 The ArrayBuffer Type |
4906 | - // |
4907 | - |
4908 | - (function() { |
4909 | - |
4910 | - function ArrayBuffer(length) { |
4911 | - length = ToInt32(length); |
4912 | - if (length < 0) throw RangeError('ArrayBuffer size is not a small enough positive integer.'); |
4913 | - Object.defineProperty(this, 'byteLength', {value: length}); |
4914 | - Object.defineProperty(this, '_bytes', {value: Array(length)}); |
4915 | - |
4916 | - for (var i = 0; i < length; i += 1) |
4917 | - this._bytes[i] = 0; |
4918 | - } |
4919 | - |
4920 | - global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer; |
4921 | - |
4922 | - // |
4923 | - // 5 The Typed Array View Types |
4924 | - // |
4925 | - |
4926 | - function $TypedArray$() { |
4927 | - |
4928 | - // %TypedArray% ( length ) |
4929 | - if (!arguments.length || typeof arguments[0] !== 'object') { |
4930 | - return (function(length) { |
4931 | - length = ToInt32(length); |
4932 | - if (length < 0) throw RangeError('length is not a small enough positive integer.'); |
4933 | - Object.defineProperty(this, 'length', {value: length}); |
4934 | - Object.defineProperty(this, 'byteLength', {value: length * this.BYTES_PER_ELEMENT}); |
4935 | - Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(this.byteLength)}); |
4936 | - Object.defineProperty(this, 'byteOffset', {value: 0}); |
4937 | - |
4938 | - }).apply(this, arguments); |
4939 | - } |
4940 | - |
4941 | - // %TypedArray% ( typedArray ) |
4942 | - if (arguments.length >= 1 && |
4943 | - Type(arguments[0]) === 'object' && |
4944 | - arguments[0] instanceof $TypedArray$) { |
4945 | - return (function(typedArray){ |
4946 | - if (this.constructor !== typedArray.constructor) throw TypeError(); |
4947 | - |
4948 | - var byteLength = typedArray.length * this.BYTES_PER_ELEMENT; |
4949 | - Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); |
4950 | - Object.defineProperty(this, 'byteLength', {value: byteLength}); |
4951 | - Object.defineProperty(this, 'byteOffset', {value: 0}); |
4952 | - Object.defineProperty(this, 'length', {value: typedArray.length}); |
4953 | - |
4954 | - for (var i = 0; i < this.length; i += 1) |
4955 | - this._setter(i, typedArray._getter(i)); |
4956 | - |
4957 | - }).apply(this, arguments); |
4958 | - } |
4959 | - |
4960 | - // %TypedArray% ( array ) |
4961 | - if (arguments.length >= 1 && |
4962 | - Type(arguments[0]) === 'object' && |
4963 | - !(arguments[0] instanceof $TypedArray$) && |
4964 | - !(arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { |
4965 | - return (function(array) { |
4966 | - |
4967 | - var byteLength = array.length * this.BYTES_PER_ELEMENT; |
4968 | - Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); |
4969 | - Object.defineProperty(this, 'byteLength', {value: byteLength}); |
4970 | - Object.defineProperty(this, 'byteOffset', {value: 0}); |
4971 | - Object.defineProperty(this, 'length', {value: array.length}); |
4972 | - |
4973 | - for (var i = 0; i < this.length; i += 1) { |
4974 | - var s = array[i]; |
4975 | - this._setter(i, Number(s)); |
4976 | - } |
4977 | - }).apply(this, arguments); |
4978 | - } |
4979 | - |
4980 | - // %TypedArray% ( buffer, byteOffset=0, length=undefined ) |
4981 | - if (arguments.length >= 1 && |
4982 | - Type(arguments[0]) === 'object' && |
4983 | - (arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { |
4984 | - return (function(buffer, byteOffset, length) { |
4985 | - |
4986 | - byteOffset = ToUint32(byteOffset); |
4987 | - if (byteOffset > buffer.byteLength) |
4988 | - throw RangeError('byteOffset out of range'); |
4989 | - |
4990 | - // The given byteOffset must be a multiple of the element |
4991 | - // size of the specific type, otherwise an exception is raised. |
4992 | - if (byteOffset % this.BYTES_PER_ELEMENT) |
4993 | - throw RangeError('buffer length minus the byteOffset is not a multiple of the element size.'); |
4994 | - |
4995 | - if (length === undefined) { |
4996 | - var byteLength = buffer.byteLength - byteOffset; |
4997 | - if (byteLength % this.BYTES_PER_ELEMENT) |
4998 | - throw RangeError('length of buffer minus byteOffset not a multiple of the element size'); |
4999 | - length = byteLength / this.BYTES_PER_ELEMENT; |
5000 | - |
The diff has been truncated for viewing.