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