diff --git a/_platforms/browser/www/svg/share-social.svg b/_platforms/browser/www/svg/share-social.svg new file mode 100644 index 000000000..102b976ee --- /dev/null +++ b/_platforms/browser/www/svg/share-social.svg @@ -0,0 +1 @@ +Share Social \ No newline at end of file diff --git a/_platforms/browser/www/svg/share.svg b/_platforms/browser/www/svg/share.svg new file mode 100644 index 000000000..975bfb134 --- /dev/null +++ b/_platforms/browser/www/svg/share.svg @@ -0,0 +1 @@ +Share \ No newline at end of file diff --git a/_platforms/browser/www/svg/shield-checkmark-outline.svg b/_platforms/browser/www/svg/shield-checkmark-outline.svg new file mode 100644 index 000000000..254f64a25 --- /dev/null +++ b/_platforms/browser/www/svg/shield-checkmark-outline.svg @@ -0,0 +1 @@ +Shield Checkmark \ No newline at end of file diff --git a/_platforms/browser/www/svg/shield-checkmark-sharp.svg b/_platforms/browser/www/svg/shield-checkmark-sharp.svg new file mode 100644 index 000000000..42ad381cb --- /dev/null +++ b/_platforms/browser/www/svg/shield-checkmark-sharp.svg @@ -0,0 +1 @@ +Shield Checkmark \ No newline at end of file diff --git a/_platforms/browser/www/svg/shield-checkmark.svg b/_platforms/browser/www/svg/shield-checkmark.svg new file mode 100644 index 000000000..5f0bfbf1f --- /dev/null +++ b/_platforms/browser/www/svg/shield-checkmark.svg @@ -0,0 +1 @@ +Shield Checkmark \ No newline at end of file diff --git a/_platforms/browser/www/svg/shield-half-outline.svg b/_platforms/browser/www/svg/shield-half-outline.svg new file mode 100644 index 000000000..01796ffa5 --- /dev/null +++ b/_platforms/browser/www/svg/shield-half-outline.svg @@ -0,0 +1 @@ +Shield Half \ No newline at end of file diff --git a/_platforms/browser/www/svg/shield-half-sharp.svg b/_platforms/browser/www/svg/shield-half-sharp.svg new file mode 100644 index 000000000..209853b65 --- /dev/null +++ b/_platforms/browser/www/svg/shield-half-sharp.svg @@ -0,0 +1 @@ +Shield Half \ No newline at end of file diff --git a/_platforms/browser/www/svg/shield-half.svg b/_platforms/browser/www/svg/shield-half.svg new file mode 100644 index 000000000..09e03b1c5 --- /dev/null +++ b/_platforms/browser/www/svg/shield-half.svg @@ -0,0 +1 @@ +Shield Half \ No newline at end of file diff --git a/_platforms/browser/www/svg/shield-outline.svg b/_platforms/browser/www/svg/shield-outline.svg new file mode 100644 index 000000000..a3310d753 --- /dev/null +++ b/_platforms/browser/www/svg/shield-outline.svg @@ -0,0 +1 @@ +Shield \ No newline at end of file diff --git a/_platforms/browser/www/svg/shield-sharp.svg b/_platforms/browser/www/svg/shield-sharp.svg new file mode 100644 index 000000000..ebb976447 --- /dev/null +++ b/_platforms/browser/www/svg/shield-sharp.svg @@ -0,0 +1 @@ +Shield \ No newline at end of file diff --git a/_platforms/browser/www/svg/shield.svg b/_platforms/browser/www/svg/shield.svg new file mode 100644 index 000000000..3102a4554 --- /dev/null +++ b/_platforms/browser/www/svg/shield.svg @@ -0,0 +1 @@ +Shield \ No newline at end of file diff --git a/_platforms/browser/www/svg/shirt-outline.svg b/_platforms/browser/www/svg/shirt-outline.svg new file mode 100644 index 000000000..d48dda0f7 --- /dev/null +++ b/_platforms/browser/www/svg/shirt-outline.svg @@ -0,0 +1 @@ +Shirt \ No newline at end of file diff --git a/_platforms/browser/www/svg/shirt-sharp.svg b/_platforms/browser/www/svg/shirt-sharp.svg new file mode 100644 index 000000000..ba20d5682 --- /dev/null +++ b/_platforms/browser/www/svg/shirt-sharp.svg @@ -0,0 +1 @@ +Shirt \ No newline at end of file diff --git a/_platforms/browser/www/svg/shirt.svg b/_platforms/browser/www/svg/shirt.svg new file mode 100644 index 000000000..b2309486e --- /dev/null +++ b/_platforms/browser/www/svg/shirt.svg @@ -0,0 +1 @@ +Shirt \ No newline at end of file diff --git a/_platforms/browser/www/svg/shuffle-outline.svg b/_platforms/browser/www/svg/shuffle-outline.svg new file mode 100644 index 000000000..93830cc10 --- /dev/null +++ b/_platforms/browser/www/svg/shuffle-outline.svg @@ -0,0 +1 @@ +Shuffle \ No newline at end of file diff --git a/_platforms/browser/www/svg/shuffle-sharp.svg b/_platforms/browser/www/svg/shuffle-sharp.svg new file mode 100644 index 000000000..79d9afb58 --- /dev/null +++ b/_platforms/browser/www/svg/shuffle-sharp.svg @@ -0,0 +1 @@ +Shuffle \ No newline at end of file diff --git a/_platforms/browser/www/svg/shuffle.svg b/_platforms/browser/www/svg/shuffle.svg new file mode 100644 index 000000000..93830cc10 --- /dev/null +++ b/_platforms/browser/www/svg/shuffle.svg @@ -0,0 +1 @@ +Shuffle \ No newline at end of file diff --git a/_platforms/browser/www/svg/skull-outline.svg b/_platforms/browser/www/svg/skull-outline.svg new file mode 100644 index 000000000..b3a0187ec --- /dev/null +++ b/_platforms/browser/www/svg/skull-outline.svg @@ -0,0 +1 @@ +Skull \ No newline at end of file diff --git a/_platforms/browser/www/svg/skull-sharp.svg b/_platforms/browser/www/svg/skull-sharp.svg new file mode 100644 index 000000000..590d46599 --- /dev/null +++ b/_platforms/browser/www/svg/skull-sharp.svg @@ -0,0 +1 @@ +Skull \ No newline at end of file diff --git a/_platforms/browser/www/svg/skull.svg b/_platforms/browser/www/svg/skull.svg new file mode 100644 index 000000000..5bec4a483 --- /dev/null +++ b/_platforms/browser/www/svg/skull.svg @@ -0,0 +1 @@ +Skull \ No newline at end of file diff --git a/_platforms/browser/www/svg/snow-outline.svg b/_platforms/browser/www/svg/snow-outline.svg new file mode 100644 index 000000000..993737b58 --- /dev/null +++ b/_platforms/browser/www/svg/snow-outline.svg @@ -0,0 +1 @@ +Snow \ No newline at end of file diff --git a/_platforms/browser/www/svg/snow-sharp.svg b/_platforms/browser/www/svg/snow-sharp.svg new file mode 100644 index 000000000..49f89e2e0 --- /dev/null +++ b/_platforms/browser/www/svg/snow-sharp.svg @@ -0,0 +1 @@ +Snow \ No newline at end of file diff --git a/_platforms/browser/www/svg/snow.svg b/_platforms/browser/www/svg/snow.svg new file mode 100644 index 000000000..6214bd432 --- /dev/null +++ b/_platforms/browser/www/svg/snow.svg @@ -0,0 +1 @@ +Snow \ No newline at end of file diff --git a/_platforms/browser/www/svg/sparkles-outline.svg b/_platforms/browser/www/svg/sparkles-outline.svg new file mode 100644 index 000000000..487401110 --- /dev/null +++ b/_platforms/browser/www/svg/sparkles-outline.svg @@ -0,0 +1 @@ +Sparkles \ No newline at end of file diff --git a/_platforms/browser/www/svg/sparkles-sharp.svg b/_platforms/browser/www/svg/sparkles-sharp.svg new file mode 100644 index 000000000..27f97aa84 --- /dev/null +++ b/_platforms/browser/www/svg/sparkles-sharp.svg @@ -0,0 +1 @@ +Sparkles \ No newline at end of file diff --git a/_platforms/browser/www/svg/sparkles.svg b/_platforms/browser/www/svg/sparkles.svg new file mode 100644 index 000000000..358a32c57 --- /dev/null +++ b/_platforms/browser/www/svg/sparkles.svg @@ -0,0 +1 @@ +Sparkles \ No newline at end of file diff --git a/_platforms/browser/www/svg/speedometer-outline.svg b/_platforms/browser/www/svg/speedometer-outline.svg new file mode 100644 index 000000000..26c73662a --- /dev/null +++ b/_platforms/browser/www/svg/speedometer-outline.svg @@ -0,0 +1 @@ +Speedometer \ No newline at end of file diff --git a/_platforms/browser/www/svg/speedometer-sharp.svg b/_platforms/browser/www/svg/speedometer-sharp.svg new file mode 100644 index 000000000..c0dd89364 --- /dev/null +++ b/_platforms/browser/www/svg/speedometer-sharp.svg @@ -0,0 +1 @@ +Speedometer \ No newline at end of file diff --git a/_platforms/browser/www/svg/speedometer.svg b/_platforms/browser/www/svg/speedometer.svg new file mode 100644 index 000000000..955fadf92 --- /dev/null +++ b/_platforms/browser/www/svg/speedometer.svg @@ -0,0 +1 @@ +Speedometer \ No newline at end of file diff --git a/_platforms/browser/www/svg/square-outline.svg b/_platforms/browser/www/svg/square-outline.svg new file mode 100644 index 000000000..e047dcdd1 --- /dev/null +++ b/_platforms/browser/www/svg/square-outline.svg @@ -0,0 +1 @@ +Square \ No newline at end of file diff --git a/_platforms/browser/www/svg/square-sharp.svg b/_platforms/browser/www/svg/square-sharp.svg new file mode 100644 index 000000000..5875442ae --- /dev/null +++ b/_platforms/browser/www/svg/square-sharp.svg @@ -0,0 +1 @@ +Square \ No newline at end of file diff --git a/_platforms/browser/www/svg/square.svg b/_platforms/browser/www/svg/square.svg new file mode 100644 index 000000000..958c04a27 --- /dev/null +++ b/_platforms/browser/www/svg/square.svg @@ -0,0 +1 @@ +Square \ No newline at end of file diff --git a/_platforms/browser/www/svg/star-half-outline.svg b/_platforms/browser/www/svg/star-half-outline.svg new file mode 100644 index 000000000..94f8b78b9 --- /dev/null +++ b/_platforms/browser/www/svg/star-half-outline.svg @@ -0,0 +1 @@ +Star Half \ No newline at end of file diff --git a/_platforms/browser/www/svg/star-half-sharp.svg b/_platforms/browser/www/svg/star-half-sharp.svg new file mode 100644 index 000000000..8968a0a14 --- /dev/null +++ b/_platforms/browser/www/svg/star-half-sharp.svg @@ -0,0 +1 @@ +Star Half \ No newline at end of file diff --git a/_platforms/browser/www/svg/star-half.svg b/_platforms/browser/www/svg/star-half.svg new file mode 100644 index 000000000..94f8b78b9 --- /dev/null +++ b/_platforms/browser/www/svg/star-half.svg @@ -0,0 +1 @@ +Star Half \ No newline at end of file diff --git a/_platforms/browser/www/svg/star-outline.svg b/_platforms/browser/www/svg/star-outline.svg new file mode 100644 index 000000000..6e7bd7dc8 --- /dev/null +++ b/_platforms/browser/www/svg/star-outline.svg @@ -0,0 +1 @@ +Star \ No newline at end of file diff --git a/_platforms/browser/www/svg/star-sharp.svg b/_platforms/browser/www/svg/star-sharp.svg new file mode 100644 index 000000000..08e663148 --- /dev/null +++ b/_platforms/browser/www/svg/star-sharp.svg @@ -0,0 +1 @@ +Star \ No newline at end of file diff --git a/_platforms/browser/www/svg/star.svg b/_platforms/browser/www/svg/star.svg new file mode 100644 index 000000000..8a1b834ce --- /dev/null +++ b/_platforms/browser/www/svg/star.svg @@ -0,0 +1 @@ +Star \ No newline at end of file diff --git a/_platforms/browser/www/svg/stats-chart-outline.svg b/_platforms/browser/www/svg/stats-chart-outline.svg new file mode 100644 index 000000000..af8102339 --- /dev/null +++ b/_platforms/browser/www/svg/stats-chart-outline.svg @@ -0,0 +1 @@ +Stats Chart \ No newline at end of file diff --git a/_platforms/browser/www/svg/stats-chart-sharp.svg b/_platforms/browser/www/svg/stats-chart-sharp.svg new file mode 100644 index 000000000..41c51374d --- /dev/null +++ b/_platforms/browser/www/svg/stats-chart-sharp.svg @@ -0,0 +1 @@ +Stats Chart \ No newline at end of file diff --git a/_platforms/browser/www/svg/stats-chart.svg b/_platforms/browser/www/svg/stats-chart.svg new file mode 100644 index 000000000..40f227ea8 --- /dev/null +++ b/_platforms/browser/www/svg/stats-chart.svg @@ -0,0 +1 @@ +Stats Chart \ No newline at end of file diff --git a/_platforms/browser/www/svg/stop-circle-outline.svg b/_platforms/browser/www/svg/stop-circle-outline.svg new file mode 100644 index 000000000..f4ae5a9b6 --- /dev/null +++ b/_platforms/browser/www/svg/stop-circle-outline.svg @@ -0,0 +1 @@ +Stop Circle \ No newline at end of file diff --git a/_platforms/browser/www/svg/stop-circle-sharp.svg b/_platforms/browser/www/svg/stop-circle-sharp.svg new file mode 100644 index 000000000..d3b448048 --- /dev/null +++ b/_platforms/browser/www/svg/stop-circle-sharp.svg @@ -0,0 +1 @@ +Stop Circle \ No newline at end of file diff --git a/_platforms/browser/www/svg/stop-circle.svg b/_platforms/browser/www/svg/stop-circle.svg new file mode 100644 index 000000000..37d7dc6a1 --- /dev/null +++ b/_platforms/browser/www/svg/stop-circle.svg @@ -0,0 +1 @@ +Stop Circle \ No newline at end of file diff --git a/_platforms/browser/www/svg/stop-outline.svg b/_platforms/browser/www/svg/stop-outline.svg new file mode 100644 index 000000000..c18fe27be --- /dev/null +++ b/_platforms/browser/www/svg/stop-outline.svg @@ -0,0 +1 @@ +Stop \ No newline at end of file diff --git a/_platforms/browser/www/svg/stop-sharp.svg b/_platforms/browser/www/svg/stop-sharp.svg new file mode 100644 index 000000000..045d338e4 --- /dev/null +++ b/_platforms/browser/www/svg/stop-sharp.svg @@ -0,0 +1 @@ +Stop \ No newline at end of file diff --git a/_platforms/browser/www/svg/stop.svg b/_platforms/browser/www/svg/stop.svg new file mode 100644 index 000000000..08645ff5a --- /dev/null +++ b/_platforms/browser/www/svg/stop.svg @@ -0,0 +1 @@ +Stop \ No newline at end of file diff --git a/_platforms/browser/www/svg/stopwatch-outline.svg b/_platforms/browser/www/svg/stopwatch-outline.svg new file mode 100644 index 000000000..d87c17e74 --- /dev/null +++ b/_platforms/browser/www/svg/stopwatch-outline.svg @@ -0,0 +1 @@ +Stopwatch \ No newline at end of file diff --git a/_platforms/browser/www/svg/stopwatch-sharp.svg b/_platforms/browser/www/svg/stopwatch-sharp.svg new file mode 100644 index 000000000..c8a6d0530 --- /dev/null +++ b/_platforms/browser/www/svg/stopwatch-sharp.svg @@ -0,0 +1 @@ +Stopwatch \ No newline at end of file diff --git a/_platforms/browser/www/svg/stopwatch.svg b/_platforms/browser/www/svg/stopwatch.svg new file mode 100644 index 000000000..624503e3f --- /dev/null +++ b/_platforms/browser/www/svg/stopwatch.svg @@ -0,0 +1 @@ +Stopwatch \ No newline at end of file diff --git a/_platforms/browser/www/svg/storefront-outline.svg b/_platforms/browser/www/svg/storefront-outline.svg new file mode 100644 index 000000000..c512e161e --- /dev/null +++ b/_platforms/browser/www/svg/storefront-outline.svg @@ -0,0 +1 @@ +Storefront \ No newline at end of file diff --git a/_platforms/browser/www/svg/storefront-sharp.svg b/_platforms/browser/www/svg/storefront-sharp.svg new file mode 100644 index 000000000..5658be23f --- /dev/null +++ b/_platforms/browser/www/svg/storefront-sharp.svg @@ -0,0 +1 @@ +Storefront \ No newline at end of file diff --git a/_platforms/browser/www/svg/storefront.svg b/_platforms/browser/www/svg/storefront.svg new file mode 100644 index 000000000..8ff2f900d --- /dev/null +++ b/_platforms/browser/www/svg/storefront.svg @@ -0,0 +1 @@ +Storefront \ No newline at end of file diff --git a/_platforms/browser/www/svg/subway-outline.svg b/_platforms/browser/www/svg/subway-outline.svg new file mode 100644 index 000000000..ecf1244a6 --- /dev/null +++ b/_platforms/browser/www/svg/subway-outline.svg @@ -0,0 +1 @@ +Subway \ No newline at end of file diff --git a/_platforms/browser/www/svg/subway-sharp.svg b/_platforms/browser/www/svg/subway-sharp.svg new file mode 100644 index 000000000..0876f7a47 --- /dev/null +++ b/_platforms/browser/www/svg/subway-sharp.svg @@ -0,0 +1 @@ +Subway \ No newline at end of file diff --git a/_platforms/browser/www/svg/subway.svg b/_platforms/browser/www/svg/subway.svg new file mode 100644 index 000000000..a8884cb8a --- /dev/null +++ b/_platforms/browser/www/svg/subway.svg @@ -0,0 +1 @@ +Subway \ No newline at end of file diff --git a/_platforms/browser/www/svg/sunny-outline.svg b/_platforms/browser/www/svg/sunny-outline.svg new file mode 100644 index 000000000..37ecf1898 --- /dev/null +++ b/_platforms/browser/www/svg/sunny-outline.svg @@ -0,0 +1 @@ +Sunny \ No newline at end of file diff --git a/_platforms/browser/www/svg/sunny-sharp.svg b/_platforms/browser/www/svg/sunny-sharp.svg new file mode 100644 index 000000000..b00a104d0 --- /dev/null +++ b/_platforms/browser/www/svg/sunny-sharp.svg @@ -0,0 +1 @@ +Sunny \ No newline at end of file diff --git a/_platforms/browser/www/svg/sunny.svg b/_platforms/browser/www/svg/sunny.svg new file mode 100644 index 000000000..2b8b16bfe --- /dev/null +++ b/_platforms/browser/www/svg/sunny.svg @@ -0,0 +1 @@ +Sunny \ No newline at end of file diff --git a/_platforms/browser/www/svg/swap-horizontal-outline.svg b/_platforms/browser/www/svg/swap-horizontal-outline.svg new file mode 100644 index 000000000..d053fad27 --- /dev/null +++ b/_platforms/browser/www/svg/swap-horizontal-outline.svg @@ -0,0 +1 @@ +Swap Horizontal \ No newline at end of file diff --git a/_platforms/browser/www/svg/swap-horizontal-sharp.svg b/_platforms/browser/www/svg/swap-horizontal-sharp.svg new file mode 100644 index 000000000..3c7af10f7 --- /dev/null +++ b/_platforms/browser/www/svg/swap-horizontal-sharp.svg @@ -0,0 +1 @@ +Swap Horizontal \ No newline at end of file diff --git a/_platforms/browser/www/svg/swap-horizontal.svg b/_platforms/browser/www/svg/swap-horizontal.svg new file mode 100644 index 000000000..d053fad27 --- /dev/null +++ b/_platforms/browser/www/svg/swap-horizontal.svg @@ -0,0 +1 @@ +Swap Horizontal \ No newline at end of file diff --git a/_platforms/browser/www/svg/swap-vertical-outline.svg b/_platforms/browser/www/svg/swap-vertical-outline.svg new file mode 100644 index 000000000..a81d25ce3 --- /dev/null +++ b/_platforms/browser/www/svg/swap-vertical-outline.svg @@ -0,0 +1 @@ +Swap Vertical \ No newline at end of file diff --git a/_platforms/browser/www/svg/swap-vertical-sharp.svg b/_platforms/browser/www/svg/swap-vertical-sharp.svg new file mode 100644 index 000000000..a61eeeb28 --- /dev/null +++ b/_platforms/browser/www/svg/swap-vertical-sharp.svg @@ -0,0 +1 @@ +Swap Vertical \ No newline at end of file diff --git a/_platforms/browser/www/svg/swap-vertical.svg b/_platforms/browser/www/svg/swap-vertical.svg new file mode 100644 index 000000000..a81d25ce3 --- /dev/null +++ b/_platforms/browser/www/svg/swap-vertical.svg @@ -0,0 +1 @@ +Swap Vertical \ No newline at end of file diff --git a/_platforms/browser/www/svg/sync-circle-outline.svg b/_platforms/browser/www/svg/sync-circle-outline.svg new file mode 100644 index 000000000..e6d11d8a6 --- /dev/null +++ b/_platforms/browser/www/svg/sync-circle-outline.svg @@ -0,0 +1 @@ +Sync Circle \ No newline at end of file diff --git a/_platforms/browser/www/svg/sync-circle-sharp.svg b/_platforms/browser/www/svg/sync-circle-sharp.svg new file mode 100644 index 000000000..37659ce20 --- /dev/null +++ b/_platforms/browser/www/svg/sync-circle-sharp.svg @@ -0,0 +1 @@ +Sync Circle \ No newline at end of file diff --git a/_platforms/browser/www/svg/sync-circle.svg b/_platforms/browser/www/svg/sync-circle.svg new file mode 100644 index 000000000..e04378274 --- /dev/null +++ b/_platforms/browser/www/svg/sync-circle.svg @@ -0,0 +1 @@ +Sync Circle \ No newline at end of file diff --git a/_platforms/browser/www/svg/sync-outline.svg b/_platforms/browser/www/svg/sync-outline.svg new file mode 100644 index 000000000..fafb9b819 --- /dev/null +++ b/_platforms/browser/www/svg/sync-outline.svg @@ -0,0 +1 @@ +Sync \ No newline at end of file diff --git a/_platforms/browser/www/svg/sync-sharp.svg b/_platforms/browser/www/svg/sync-sharp.svg new file mode 100644 index 000000000..7742a3a4c --- /dev/null +++ b/_platforms/browser/www/svg/sync-sharp.svg @@ -0,0 +1 @@ +Sync \ No newline at end of file diff --git a/_platforms/browser/www/svg/sync.svg b/_platforms/browser/www/svg/sync.svg new file mode 100644 index 000000000..fafb9b819 --- /dev/null +++ b/_platforms/browser/www/svg/sync.svg @@ -0,0 +1 @@ +Sync \ No newline at end of file diff --git a/_platforms/browser/www/svg/tablet-landscape-outline.svg b/_platforms/browser/www/svg/tablet-landscape-outline.svg new file mode 100644 index 000000000..0e8316136 --- /dev/null +++ b/_platforms/browser/www/svg/tablet-landscape-outline.svg @@ -0,0 +1 @@ +Tablet Landscape \ No newline at end of file diff --git a/_platforms/browser/www/svg/tablet-landscape-sharp.svg b/_platforms/browser/www/svg/tablet-landscape-sharp.svg new file mode 100644 index 000000000..526e1b90e --- /dev/null +++ b/_platforms/browser/www/svg/tablet-landscape-sharp.svg @@ -0,0 +1 @@ +Tablet Landscape \ No newline at end of file diff --git a/_platforms/browser/www/svg/tablet-landscape.svg b/_platforms/browser/www/svg/tablet-landscape.svg new file mode 100644 index 000000000..f98df63de --- /dev/null +++ b/_platforms/browser/www/svg/tablet-landscape.svg @@ -0,0 +1 @@ +Tablet Landscape \ No newline at end of file diff --git a/_platforms/browser/www/svg/tablet-portrait-outline.svg b/_platforms/browser/www/svg/tablet-portrait-outline.svg new file mode 100644 index 000000000..26bd9dca9 --- /dev/null +++ b/_platforms/browser/www/svg/tablet-portrait-outline.svg @@ -0,0 +1 @@ +Tablet Portrait \ No newline at end of file diff --git a/_platforms/browser/www/svg/tablet-portrait-sharp.svg b/_platforms/browser/www/svg/tablet-portrait-sharp.svg new file mode 100644 index 000000000..0289d6339 --- /dev/null +++ b/_platforms/browser/www/svg/tablet-portrait-sharp.svg @@ -0,0 +1 @@ +Tablet Portrait \ No newline at end of file diff --git a/_platforms/browser/www/svg/tablet-portrait.svg b/_platforms/browser/www/svg/tablet-portrait.svg new file mode 100644 index 000000000..03e91a7a5 --- /dev/null +++ b/_platforms/browser/www/svg/tablet-portrait.svg @@ -0,0 +1 @@ +Tablet Portrait \ No newline at end of file diff --git a/_platforms/browser/www/svg/telescope-outline.svg b/_platforms/browser/www/svg/telescope-outline.svg new file mode 100644 index 000000000..9b0b63e8d --- /dev/null +++ b/_platforms/browser/www/svg/telescope-outline.svg @@ -0,0 +1 @@ +Telescope \ No newline at end of file diff --git a/_platforms/browser/www/svg/telescope-sharp.svg b/_platforms/browser/www/svg/telescope-sharp.svg new file mode 100644 index 000000000..b3c30e3ac --- /dev/null +++ b/_platforms/browser/www/svg/telescope-sharp.svg @@ -0,0 +1 @@ +Telescope \ No newline at end of file diff --git a/_platforms/browser/www/svg/telescope.svg b/_platforms/browser/www/svg/telescope.svg new file mode 100644 index 000000000..64f6bc51c --- /dev/null +++ b/_platforms/browser/www/svg/telescope.svg @@ -0,0 +1 @@ +Telescope \ No newline at end of file diff --git a/_platforms/browser/www/svg/tennisball-outline.svg b/_platforms/browser/www/svg/tennisball-outline.svg new file mode 100644 index 000000000..8384c4de7 --- /dev/null +++ b/_platforms/browser/www/svg/tennisball-outline.svg @@ -0,0 +1 @@ +Tennisball \ No newline at end of file diff --git a/_platforms/browser/www/svg/tennisball-sharp.svg b/_platforms/browser/www/svg/tennisball-sharp.svg new file mode 100644 index 000000000..79d84080b --- /dev/null +++ b/_platforms/browser/www/svg/tennisball-sharp.svg @@ -0,0 +1 @@ +Tennisball \ No newline at end of file diff --git a/_platforms/browser/www/svg/tennisball.svg b/_platforms/browser/www/svg/tennisball.svg new file mode 100644 index 000000000..727ccc95b --- /dev/null +++ b/_platforms/browser/www/svg/tennisball.svg @@ -0,0 +1 @@ +Tennisball \ No newline at end of file diff --git a/_platforms/browser/www/svg/terminal-outline.svg b/_platforms/browser/www/svg/terminal-outline.svg new file mode 100644 index 000000000..2a2aa069f --- /dev/null +++ b/_platforms/browser/www/svg/terminal-outline.svg @@ -0,0 +1 @@ +Terminal \ No newline at end of file diff --git a/_platforms/browser/www/svg/terminal-sharp.svg b/_platforms/browser/www/svg/terminal-sharp.svg new file mode 100644 index 000000000..ee5bd4f40 --- /dev/null +++ b/_platforms/browser/www/svg/terminal-sharp.svg @@ -0,0 +1 @@ +Terminal \ No newline at end of file diff --git a/_platforms/browser/www/svg/terminal.svg b/_platforms/browser/www/svg/terminal.svg new file mode 100644 index 000000000..d5840da2c --- /dev/null +++ b/_platforms/browser/www/svg/terminal.svg @@ -0,0 +1 @@ +Terminal \ No newline at end of file diff --git a/_platforms/browser/www/svg/text-outline.svg b/_platforms/browser/www/svg/text-outline.svg new file mode 100644 index 000000000..4a00cedd9 --- /dev/null +++ b/_platforms/browser/www/svg/text-outline.svg @@ -0,0 +1 @@ +Text \ No newline at end of file diff --git a/_platforms/browser/www/svg/text-sharp.svg b/_platforms/browser/www/svg/text-sharp.svg new file mode 100644 index 000000000..a1e7d9fd3 --- /dev/null +++ b/_platforms/browser/www/svg/text-sharp.svg @@ -0,0 +1 @@ +Text \ No newline at end of file diff --git a/_platforms/browser/www/svg/text.svg b/_platforms/browser/www/svg/text.svg new file mode 100644 index 000000000..0a96ad2ba --- /dev/null +++ b/_platforms/browser/www/svg/text.svg @@ -0,0 +1 @@ +Text \ No newline at end of file diff --git a/_platforms/browser/www/svg/thermometer-outline.svg b/_platforms/browser/www/svg/thermometer-outline.svg new file mode 100644 index 000000000..793fb6ef3 --- /dev/null +++ b/_platforms/browser/www/svg/thermometer-outline.svg @@ -0,0 +1 @@ +Thermometer \ No newline at end of file diff --git a/_platforms/browser/www/svg/thermometer-sharp.svg b/_platforms/browser/www/svg/thermometer-sharp.svg new file mode 100644 index 000000000..b50637f81 --- /dev/null +++ b/_platforms/browser/www/svg/thermometer-sharp.svg @@ -0,0 +1 @@ +Thermometer \ No newline at end of file diff --git a/_platforms/browser/www/svg/thermometer.svg b/_platforms/browser/www/svg/thermometer.svg new file mode 100644 index 000000000..17a07a21e --- /dev/null +++ b/_platforms/browser/www/svg/thermometer.svg @@ -0,0 +1 @@ +Thermometer \ No newline at end of file diff --git a/_platforms/browser/www/svg/thumbs-down-outline.svg b/_platforms/browser/www/svg/thumbs-down-outline.svg new file mode 100644 index 000000000..1d6eac560 --- /dev/null +++ b/_platforms/browser/www/svg/thumbs-down-outline.svg @@ -0,0 +1 @@ +Thumbs Down \ No newline at end of file diff --git a/_platforms/browser/www/svg/thumbs-down-sharp.svg b/_platforms/browser/www/svg/thumbs-down-sharp.svg new file mode 100644 index 000000000..664d36a4f --- /dev/null +++ b/_platforms/browser/www/svg/thumbs-down-sharp.svg @@ -0,0 +1 @@ +Thumbs Down \ No newline at end of file diff --git a/_platforms/browser/www/svg/thumbs-down.svg b/_platforms/browser/www/svg/thumbs-down.svg new file mode 100644 index 000000000..c1e5f47cd --- /dev/null +++ b/_platforms/browser/www/svg/thumbs-down.svg @@ -0,0 +1 @@ +Thumbs Down \ No newline at end of file diff --git a/_platforms/browser/www/svg/thumbs-up-outline.svg b/_platforms/browser/www/svg/thumbs-up-outline.svg new file mode 100644 index 000000000..6e7f9726a --- /dev/null +++ b/_platforms/browser/www/svg/thumbs-up-outline.svg @@ -0,0 +1 @@ +Thumbs Up \ No newline at end of file diff --git a/_platforms/browser/www/svg/thumbs-up-sharp.svg b/_platforms/browser/www/svg/thumbs-up-sharp.svg new file mode 100644 index 000000000..dbaa650a6 --- /dev/null +++ b/_platforms/browser/www/svg/thumbs-up-sharp.svg @@ -0,0 +1 @@ +Thumbs Up \ No newline at end of file diff --git a/_platforms/browser/www/svg/thumbs-up.svg b/_platforms/browser/www/svg/thumbs-up.svg new file mode 100644 index 000000000..1c9c8a4b6 --- /dev/null +++ b/_platforms/browser/www/svg/thumbs-up.svg @@ -0,0 +1 @@ +Thumbs Up \ No newline at end of file diff --git a/_platforms/browser/www/svg/thunderstorm-outline.svg b/_platforms/browser/www/svg/thunderstorm-outline.svg new file mode 100644 index 000000000..5366c7084 --- /dev/null +++ b/_platforms/browser/www/svg/thunderstorm-outline.svg @@ -0,0 +1 @@ +Thunderstorm \ No newline at end of file diff --git a/_platforms/browser/www/svg/thunderstorm-sharp.svg b/_platforms/browser/www/svg/thunderstorm-sharp.svg new file mode 100644 index 000000000..d8a18a0bf --- /dev/null +++ b/_platforms/browser/www/svg/thunderstorm-sharp.svg @@ -0,0 +1 @@ +Thunderstorm \ No newline at end of file diff --git a/_platforms/browser/www/svg/thunderstorm.svg b/_platforms/browser/www/svg/thunderstorm.svg new file mode 100644 index 000000000..5211337fe --- /dev/null +++ b/_platforms/browser/www/svg/thunderstorm.svg @@ -0,0 +1 @@ +Thunderstorm \ No newline at end of file diff --git a/_platforms/browser/www/svg/ticket-outline.svg b/_platforms/browser/www/svg/ticket-outline.svg new file mode 100644 index 000000000..3584d2fde --- /dev/null +++ b/_platforms/browser/www/svg/ticket-outline.svg @@ -0,0 +1 @@ +Ticket \ No newline at end of file diff --git a/_platforms/browser/www/svg/ticket-sharp.svg b/_platforms/browser/www/svg/ticket-sharp.svg new file mode 100644 index 000000000..8daa57152 --- /dev/null +++ b/_platforms/browser/www/svg/ticket-sharp.svg @@ -0,0 +1 @@ +Ticket \ No newline at end of file diff --git a/_platforms/browser/www/svg/ticket.svg b/_platforms/browser/www/svg/ticket.svg new file mode 100644 index 000000000..65a7a8a91 --- /dev/null +++ b/_platforms/browser/www/svg/ticket.svg @@ -0,0 +1 @@ +Ticket \ No newline at end of file diff --git a/_platforms/browser/www/svg/time-outline.svg b/_platforms/browser/www/svg/time-outline.svg new file mode 100644 index 000000000..7a5c0d438 --- /dev/null +++ b/_platforms/browser/www/svg/time-outline.svg @@ -0,0 +1 @@ +Time \ No newline at end of file diff --git a/_platforms/browser/www/svg/time-sharp.svg b/_platforms/browser/www/svg/time-sharp.svg new file mode 100644 index 000000000..ec0fbeb29 --- /dev/null +++ b/_platforms/browser/www/svg/time-sharp.svg @@ -0,0 +1 @@ +Time \ No newline at end of file diff --git a/_platforms/browser/www/svg/time.svg b/_platforms/browser/www/svg/time.svg new file mode 100644 index 000000000..7c15eac8d --- /dev/null +++ b/_platforms/browser/www/svg/time.svg @@ -0,0 +1 @@ +Time \ No newline at end of file diff --git a/_platforms/browser/www/svg/timer-outline.svg b/_platforms/browser/www/svg/timer-outline.svg new file mode 100644 index 000000000..5c6752884 --- /dev/null +++ b/_platforms/browser/www/svg/timer-outline.svg @@ -0,0 +1 @@ +Timer \ No newline at end of file diff --git a/_platforms/browser/www/svg/timer-sharp.svg b/_platforms/browser/www/svg/timer-sharp.svg new file mode 100644 index 000000000..1b30ffe24 --- /dev/null +++ b/_platforms/browser/www/svg/timer-sharp.svg @@ -0,0 +1 @@ +Timer \ No newline at end of file diff --git a/_platforms/browser/www/svg/timer.svg b/_platforms/browser/www/svg/timer.svg new file mode 100644 index 000000000..abf8aaf3f --- /dev/null +++ b/_platforms/browser/www/svg/timer.svg @@ -0,0 +1 @@ +Timer \ No newline at end of file diff --git a/_platforms/browser/www/svg/today-outline.svg b/_platforms/browser/www/svg/today-outline.svg new file mode 100644 index 000000000..b0a798658 --- /dev/null +++ b/_platforms/browser/www/svg/today-outline.svg @@ -0,0 +1 @@ +Today \ No newline at end of file diff --git a/_platforms/browser/www/svg/today-sharp.svg b/_platforms/browser/www/svg/today-sharp.svg new file mode 100644 index 000000000..32a045bcb --- /dev/null +++ b/_platforms/browser/www/svg/today-sharp.svg @@ -0,0 +1 @@ +Today \ No newline at end of file diff --git a/_platforms/browser/www/svg/today.svg b/_platforms/browser/www/svg/today.svg new file mode 100644 index 000000000..42752fd1f --- /dev/null +++ b/_platforms/browser/www/svg/today.svg @@ -0,0 +1 @@ +Today \ No newline at end of file diff --git a/_platforms/browser/www/svg/toggle-outline.svg b/_platforms/browser/www/svg/toggle-outline.svg new file mode 100644 index 000000000..e1597abb5 --- /dev/null +++ b/_platforms/browser/www/svg/toggle-outline.svg @@ -0,0 +1 @@ +Toggle \ No newline at end of file diff --git a/_platforms/browser/www/svg/toggle-sharp.svg b/_platforms/browser/www/svg/toggle-sharp.svg new file mode 100644 index 000000000..ca73f245a --- /dev/null +++ b/_platforms/browser/www/svg/toggle-sharp.svg @@ -0,0 +1 @@ +Toggle \ No newline at end of file diff --git a/_platforms/browser/www/svg/toggle.svg b/_platforms/browser/www/svg/toggle.svg new file mode 100644 index 000000000..3db43e88d --- /dev/null +++ b/_platforms/browser/www/svg/toggle.svg @@ -0,0 +1 @@ +Toggle \ No newline at end of file diff --git a/_platforms/browser/www/svg/trail-sign-outline.svg b/_platforms/browser/www/svg/trail-sign-outline.svg new file mode 100644 index 000000000..8ab4e92e1 --- /dev/null +++ b/_platforms/browser/www/svg/trail-sign-outline.svg @@ -0,0 +1 @@ +Trail Sign \ No newline at end of file diff --git a/_platforms/browser/www/svg/trail-sign-sharp.svg b/_platforms/browser/www/svg/trail-sign-sharp.svg new file mode 100644 index 000000000..30063de8a --- /dev/null +++ b/_platforms/browser/www/svg/trail-sign-sharp.svg @@ -0,0 +1 @@ +Trail Sign \ No newline at end of file diff --git a/_platforms/browser/www/svg/trail-sign.svg b/_platforms/browser/www/svg/trail-sign.svg new file mode 100644 index 000000000..adbc92997 --- /dev/null +++ b/_platforms/browser/www/svg/trail-sign.svg @@ -0,0 +1 @@ +Trail Sign \ No newline at end of file diff --git a/_platforms/browser/www/svg/train-outline.svg b/_platforms/browser/www/svg/train-outline.svg new file mode 100644 index 000000000..ffffd0c63 --- /dev/null +++ b/_platforms/browser/www/svg/train-outline.svg @@ -0,0 +1 @@ +Train \ No newline at end of file diff --git a/_platforms/browser/www/svg/train-sharp.svg b/_platforms/browser/www/svg/train-sharp.svg new file mode 100644 index 000000000..2ee09361e --- /dev/null +++ b/_platforms/browser/www/svg/train-sharp.svg @@ -0,0 +1 @@ +Train \ No newline at end of file diff --git a/_platforms/browser/www/svg/train.svg b/_platforms/browser/www/svg/train.svg new file mode 100644 index 000000000..c1424c693 --- /dev/null +++ b/_platforms/browser/www/svg/train.svg @@ -0,0 +1 @@ +Train \ No newline at end of file diff --git a/_platforms/browser/www/svg/transgender-outline.svg b/_platforms/browser/www/svg/transgender-outline.svg new file mode 100644 index 000000000..8bd2c521e --- /dev/null +++ b/_platforms/browser/www/svg/transgender-outline.svg @@ -0,0 +1 @@ +Transgender \ No newline at end of file diff --git a/_platforms/browser/www/svg/transgender-sharp.svg b/_platforms/browser/www/svg/transgender-sharp.svg new file mode 100644 index 000000000..735bba159 --- /dev/null +++ b/_platforms/browser/www/svg/transgender-sharp.svg @@ -0,0 +1 @@ +Transgender \ No newline at end of file diff --git a/_platforms/browser/www/svg/transgender.svg b/_platforms/browser/www/svg/transgender.svg new file mode 100644 index 000000000..c41d40fb6 --- /dev/null +++ b/_platforms/browser/www/svg/transgender.svg @@ -0,0 +1 @@ +Transgender \ No newline at end of file diff --git a/_platforms/browser/www/svg/trash-bin-outline.svg b/_platforms/browser/www/svg/trash-bin-outline.svg new file mode 100644 index 000000000..0e5919b1a --- /dev/null +++ b/_platforms/browser/www/svg/trash-bin-outline.svg @@ -0,0 +1 @@ +Trash Bin \ No newline at end of file diff --git a/_platforms/browser/www/svg/trash-bin-sharp.svg b/_platforms/browser/www/svg/trash-bin-sharp.svg new file mode 100644 index 000000000..125c3d2d1 --- /dev/null +++ b/_platforms/browser/www/svg/trash-bin-sharp.svg @@ -0,0 +1 @@ +Trash Bin \ No newline at end of file diff --git a/_platforms/browser/www/svg/trash-bin.svg b/_platforms/browser/www/svg/trash-bin.svg new file mode 100644 index 000000000..ec215868e --- /dev/null +++ b/_platforms/browser/www/svg/trash-bin.svg @@ -0,0 +1 @@ +Trash Bin \ No newline at end of file diff --git a/_platforms/browser/www/svg/trash-outline.svg b/_platforms/browser/www/svg/trash-outline.svg new file mode 100644 index 000000000..d7aa6fa89 --- /dev/null +++ b/_platforms/browser/www/svg/trash-outline.svg @@ -0,0 +1 @@ +Trash \ No newline at end of file diff --git a/_platforms/browser/www/svg/trash-sharp.svg b/_platforms/browser/www/svg/trash-sharp.svg new file mode 100644 index 000000000..dbdbbf4f0 --- /dev/null +++ b/_platforms/browser/www/svg/trash-sharp.svg @@ -0,0 +1 @@ +Trash \ No newline at end of file diff --git a/_platforms/browser/www/svg/trash.svg b/_platforms/browser/www/svg/trash.svg new file mode 100644 index 000000000..799309241 --- /dev/null +++ b/_platforms/browser/www/svg/trash.svg @@ -0,0 +1 @@ +Trash \ No newline at end of file diff --git a/_platforms/browser/www/svg/trending-down-outline.svg b/_platforms/browser/www/svg/trending-down-outline.svg new file mode 100644 index 000000000..e2a85cf25 --- /dev/null +++ b/_platforms/browser/www/svg/trending-down-outline.svg @@ -0,0 +1 @@ +Trending Down \ No newline at end of file diff --git a/_platforms/browser/www/svg/trending-down-sharp.svg b/_platforms/browser/www/svg/trending-down-sharp.svg new file mode 100644 index 000000000..e1a4595d2 --- /dev/null +++ b/_platforms/browser/www/svg/trending-down-sharp.svg @@ -0,0 +1 @@ +Trending Down \ No newline at end of file diff --git a/_platforms/browser/www/svg/trending-down.svg b/_platforms/browser/www/svg/trending-down.svg new file mode 100644 index 000000000..e2a85cf25 --- /dev/null +++ b/_platforms/browser/www/svg/trending-down.svg @@ -0,0 +1 @@ +Trending Down \ No newline at end of file diff --git a/_platforms/browser/www/svg/trending-up-outline.svg b/_platforms/browser/www/svg/trending-up-outline.svg new file mode 100644 index 000000000..e161a765d --- /dev/null +++ b/_platforms/browser/www/svg/trending-up-outline.svg @@ -0,0 +1 @@ +Trending Up \ No newline at end of file diff --git a/_platforms/browser/www/svg/trending-up-sharp.svg b/_platforms/browser/www/svg/trending-up-sharp.svg new file mode 100644 index 000000000..ef01a3c7b --- /dev/null +++ b/_platforms/browser/www/svg/trending-up-sharp.svg @@ -0,0 +1 @@ +Trending Up \ No newline at end of file diff --git a/_platforms/browser/www/svg/trending-up.svg b/_platforms/browser/www/svg/trending-up.svg new file mode 100644 index 000000000..e161a765d --- /dev/null +++ b/_platforms/browser/www/svg/trending-up.svg @@ -0,0 +1 @@ +Trending Up \ No newline at end of file diff --git a/_platforms/browser/www/svg/triangle-outline.svg b/_platforms/browser/www/svg/triangle-outline.svg new file mode 100644 index 000000000..d6f49bf01 --- /dev/null +++ b/_platforms/browser/www/svg/triangle-outline.svg @@ -0,0 +1 @@ +Triangle \ No newline at end of file diff --git a/_platforms/browser/www/svg/triangle-sharp.svg b/_platforms/browser/www/svg/triangle-sharp.svg new file mode 100644 index 000000000..dbd706cc6 --- /dev/null +++ b/_platforms/browser/www/svg/triangle-sharp.svg @@ -0,0 +1 @@ +Triangle \ No newline at end of file diff --git a/_platforms/browser/www/svg/triangle.svg b/_platforms/browser/www/svg/triangle.svg new file mode 100644 index 000000000..83ed4a57d --- /dev/null +++ b/_platforms/browser/www/svg/triangle.svg @@ -0,0 +1 @@ +Triangle \ No newline at end of file diff --git a/_platforms/browser/www/svg/trophy-outline.svg b/_platforms/browser/www/svg/trophy-outline.svg new file mode 100644 index 000000000..177d29776 --- /dev/null +++ b/_platforms/browser/www/svg/trophy-outline.svg @@ -0,0 +1 @@ +Trophy \ No newline at end of file diff --git a/_platforms/browser/www/svg/trophy-sharp.svg b/_platforms/browser/www/svg/trophy-sharp.svg new file mode 100644 index 000000000..d28a5531c --- /dev/null +++ b/_platforms/browser/www/svg/trophy-sharp.svg @@ -0,0 +1 @@ +Trophy \ No newline at end of file diff --git a/_platforms/browser/www/svg/trophy.svg b/_platforms/browser/www/svg/trophy.svg new file mode 100644 index 000000000..3a1617168 --- /dev/null +++ b/_platforms/browser/www/svg/trophy.svg @@ -0,0 +1 @@ +Trophy \ No newline at end of file diff --git a/_platforms/browser/www/svg/tv-outline.svg b/_platforms/browser/www/svg/tv-outline.svg new file mode 100644 index 000000000..82de036fb --- /dev/null +++ b/_platforms/browser/www/svg/tv-outline.svg @@ -0,0 +1 @@ +Tv \ No newline at end of file diff --git a/_platforms/browser/www/svg/tv-sharp.svg b/_platforms/browser/www/svg/tv-sharp.svg new file mode 100644 index 000000000..466a4d7fe --- /dev/null +++ b/_platforms/browser/www/svg/tv-sharp.svg @@ -0,0 +1 @@ +Tv \ No newline at end of file diff --git a/_platforms/browser/www/svg/tv.svg b/_platforms/browser/www/svg/tv.svg new file mode 100644 index 000000000..20c91a8fa --- /dev/null +++ b/_platforms/browser/www/svg/tv.svg @@ -0,0 +1 @@ +Tv \ No newline at end of file diff --git a/_platforms/browser/www/svg/umbrella-outline.svg b/_platforms/browser/www/svg/umbrella-outline.svg new file mode 100644 index 000000000..0a5a23415 --- /dev/null +++ b/_platforms/browser/www/svg/umbrella-outline.svg @@ -0,0 +1 @@ +Umbrella \ No newline at end of file diff --git a/_platforms/browser/www/svg/umbrella-sharp.svg b/_platforms/browser/www/svg/umbrella-sharp.svg new file mode 100644 index 000000000..6b03d047c --- /dev/null +++ b/_platforms/browser/www/svg/umbrella-sharp.svg @@ -0,0 +1 @@ +Umbrella \ No newline at end of file diff --git a/_platforms/browser/www/svg/umbrella.svg b/_platforms/browser/www/svg/umbrella.svg new file mode 100644 index 000000000..18fc5304b --- /dev/null +++ b/_platforms/browser/www/svg/umbrella.svg @@ -0,0 +1 @@ +Umbrella \ No newline at end of file diff --git a/_platforms/browser/www/svg/unlink-outline.svg b/_platforms/browser/www/svg/unlink-outline.svg new file mode 100644 index 000000000..d67f8dd95 --- /dev/null +++ b/_platforms/browser/www/svg/unlink-outline.svg @@ -0,0 +1 @@ +Unlink \ No newline at end of file diff --git a/_platforms/browser/www/svg/unlink-sharp.svg b/_platforms/browser/www/svg/unlink-sharp.svg new file mode 100644 index 000000000..42fad8876 --- /dev/null +++ b/_platforms/browser/www/svg/unlink-sharp.svg @@ -0,0 +1 @@ +Unlink \ No newline at end of file diff --git a/_platforms/browser/www/svg/unlink.svg b/_platforms/browser/www/svg/unlink.svg new file mode 100644 index 000000000..864a2f8f7 --- /dev/null +++ b/_platforms/browser/www/svg/unlink.svg @@ -0,0 +1 @@ +Unlink \ No newline at end of file diff --git a/_platforms/browser/www/svg/videocam-off-outline.svg b/_platforms/browser/www/svg/videocam-off-outline.svg new file mode 100644 index 000000000..0a13d0e4f --- /dev/null +++ b/_platforms/browser/www/svg/videocam-off-outline.svg @@ -0,0 +1 @@ +Videocam Off \ No newline at end of file diff --git a/_platforms/browser/www/svg/videocam-off-sharp.svg b/_platforms/browser/www/svg/videocam-off-sharp.svg new file mode 100644 index 000000000..9c8b9c570 --- /dev/null +++ b/_platforms/browser/www/svg/videocam-off-sharp.svg @@ -0,0 +1 @@ +Videocam Off \ No newline at end of file diff --git a/_platforms/browser/www/svg/videocam-off.svg b/_platforms/browser/www/svg/videocam-off.svg new file mode 100644 index 000000000..fff3b1079 --- /dev/null +++ b/_platforms/browser/www/svg/videocam-off.svg @@ -0,0 +1 @@ +Videocam Off \ No newline at end of file diff --git a/_platforms/browser/www/svg/videocam-outline.svg b/_platforms/browser/www/svg/videocam-outline.svg new file mode 100644 index 000000000..1e885e124 --- /dev/null +++ b/_platforms/browser/www/svg/videocam-outline.svg @@ -0,0 +1 @@ +Videocam \ No newline at end of file diff --git a/_platforms/browser/www/svg/videocam-sharp.svg b/_platforms/browser/www/svg/videocam-sharp.svg new file mode 100644 index 000000000..42fe6f79b --- /dev/null +++ b/_platforms/browser/www/svg/videocam-sharp.svg @@ -0,0 +1 @@ +Videocam \ No newline at end of file diff --git a/_platforms/browser/www/svg/videocam.svg b/_platforms/browser/www/svg/videocam.svg new file mode 100644 index 000000000..09f750716 --- /dev/null +++ b/_platforms/browser/www/svg/videocam.svg @@ -0,0 +1 @@ +Videocam \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-high-outline.svg b/_platforms/browser/www/svg/volume-high-outline.svg new file mode 100644 index 000000000..ab6c9ffbe --- /dev/null +++ b/_platforms/browser/www/svg/volume-high-outline.svg @@ -0,0 +1 @@ +Volume High \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-high-sharp.svg b/_platforms/browser/www/svg/volume-high-sharp.svg new file mode 100644 index 000000000..83f5ad557 --- /dev/null +++ b/_platforms/browser/www/svg/volume-high-sharp.svg @@ -0,0 +1 @@ +Volume High \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-high.svg b/_platforms/browser/www/svg/volume-high.svg new file mode 100644 index 000000000..4b843c5d0 --- /dev/null +++ b/_platforms/browser/www/svg/volume-high.svg @@ -0,0 +1 @@ +Volume High \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-low-outline.svg b/_platforms/browser/www/svg/volume-low-outline.svg new file mode 100644 index 000000000..ba7d50440 --- /dev/null +++ b/_platforms/browser/www/svg/volume-low-outline.svg @@ -0,0 +1 @@ +Volume Low \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-low-sharp.svg b/_platforms/browser/www/svg/volume-low-sharp.svg new file mode 100644 index 000000000..62cea5890 --- /dev/null +++ b/_platforms/browser/www/svg/volume-low-sharp.svg @@ -0,0 +1 @@ +Volume Low \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-low.svg b/_platforms/browser/www/svg/volume-low.svg new file mode 100644 index 000000000..40cd39a73 --- /dev/null +++ b/_platforms/browser/www/svg/volume-low.svg @@ -0,0 +1 @@ +Volume Low \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-medium-outline.svg b/_platforms/browser/www/svg/volume-medium-outline.svg new file mode 100644 index 000000000..c05b4923e --- /dev/null +++ b/_platforms/browser/www/svg/volume-medium-outline.svg @@ -0,0 +1 @@ +Volume Medium \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-medium-sharp.svg b/_platforms/browser/www/svg/volume-medium-sharp.svg new file mode 100644 index 000000000..0f38156df --- /dev/null +++ b/_platforms/browser/www/svg/volume-medium-sharp.svg @@ -0,0 +1 @@ +Volume Medium \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-medium.svg b/_platforms/browser/www/svg/volume-medium.svg new file mode 100644 index 000000000..345702265 --- /dev/null +++ b/_platforms/browser/www/svg/volume-medium.svg @@ -0,0 +1 @@ +Volume Medium \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-mute-outline.svg b/_platforms/browser/www/svg/volume-mute-outline.svg new file mode 100644 index 000000000..3548fee57 --- /dev/null +++ b/_platforms/browser/www/svg/volume-mute-outline.svg @@ -0,0 +1 @@ +Volume Mute \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-mute-sharp.svg b/_platforms/browser/www/svg/volume-mute-sharp.svg new file mode 100644 index 000000000..0a23ad63f --- /dev/null +++ b/_platforms/browser/www/svg/volume-mute-sharp.svg @@ -0,0 +1 @@ +Volume Mute \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-mute.svg b/_platforms/browser/www/svg/volume-mute.svg new file mode 100644 index 000000000..1f7e80bfe --- /dev/null +++ b/_platforms/browser/www/svg/volume-mute.svg @@ -0,0 +1 @@ +Volume Mute \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-off-outline.svg b/_platforms/browser/www/svg/volume-off-outline.svg new file mode 100644 index 000000000..72ebf3c91 --- /dev/null +++ b/_platforms/browser/www/svg/volume-off-outline.svg @@ -0,0 +1 @@ +Volume Off \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-off-sharp.svg b/_platforms/browser/www/svg/volume-off-sharp.svg new file mode 100644 index 000000000..7e0f1948d --- /dev/null +++ b/_platforms/browser/www/svg/volume-off-sharp.svg @@ -0,0 +1 @@ +Volume Off \ No newline at end of file diff --git a/_platforms/browser/www/svg/volume-off.svg b/_platforms/browser/www/svg/volume-off.svg new file mode 100644 index 000000000..ce21e2221 --- /dev/null +++ b/_platforms/browser/www/svg/volume-off.svg @@ -0,0 +1 @@ +Volume Off \ No newline at end of file diff --git a/_platforms/browser/www/svg/walk-outline.svg b/_platforms/browser/www/svg/walk-outline.svg new file mode 100644 index 000000000..5f2b50e83 --- /dev/null +++ b/_platforms/browser/www/svg/walk-outline.svg @@ -0,0 +1 @@ +Walk \ No newline at end of file diff --git a/_platforms/browser/www/svg/walk-sharp.svg b/_platforms/browser/www/svg/walk-sharp.svg new file mode 100644 index 000000000..7099bcded --- /dev/null +++ b/_platforms/browser/www/svg/walk-sharp.svg @@ -0,0 +1 @@ +Walk \ No newline at end of file diff --git a/_platforms/browser/www/svg/walk.svg b/_platforms/browser/www/svg/walk.svg new file mode 100644 index 000000000..b548e93c1 --- /dev/null +++ b/_platforms/browser/www/svg/walk.svg @@ -0,0 +1 @@ +Walk \ No newline at end of file diff --git a/_platforms/browser/www/svg/wallet-outline.svg b/_platforms/browser/www/svg/wallet-outline.svg new file mode 100644 index 000000000..0f0f0b7c0 --- /dev/null +++ b/_platforms/browser/www/svg/wallet-outline.svg @@ -0,0 +1 @@ +Wallet \ No newline at end of file diff --git a/_platforms/browser/www/svg/wallet-sharp.svg b/_platforms/browser/www/svg/wallet-sharp.svg new file mode 100644 index 000000000..25d6688d7 --- /dev/null +++ b/_platforms/browser/www/svg/wallet-sharp.svg @@ -0,0 +1 @@ +Wallet \ No newline at end of file diff --git a/_platforms/browser/www/svg/wallet.svg b/_platforms/browser/www/svg/wallet.svg new file mode 100644 index 000000000..434b08c9e --- /dev/null +++ b/_platforms/browser/www/svg/wallet.svg @@ -0,0 +1 @@ +Wallet \ No newline at end of file diff --git a/_platforms/browser/www/svg/warning-outline.svg b/_platforms/browser/www/svg/warning-outline.svg new file mode 100644 index 000000000..52b059483 --- /dev/null +++ b/_platforms/browser/www/svg/warning-outline.svg @@ -0,0 +1 @@ +Warning \ No newline at end of file diff --git a/_platforms/browser/www/svg/warning-sharp.svg b/_platforms/browser/www/svg/warning-sharp.svg new file mode 100644 index 000000000..4376c6f12 --- /dev/null +++ b/_platforms/browser/www/svg/warning-sharp.svg @@ -0,0 +1 @@ +Warning \ No newline at end of file diff --git a/_platforms/browser/www/svg/warning.svg b/_platforms/browser/www/svg/warning.svg new file mode 100644 index 000000000..c77044505 --- /dev/null +++ b/_platforms/browser/www/svg/warning.svg @@ -0,0 +1 @@ +Warning \ No newline at end of file diff --git a/_platforms/browser/www/svg/watch-outline.svg b/_platforms/browser/www/svg/watch-outline.svg new file mode 100644 index 000000000..8c7331094 --- /dev/null +++ b/_platforms/browser/www/svg/watch-outline.svg @@ -0,0 +1 @@ +Watch \ No newline at end of file diff --git a/_platforms/browser/www/svg/watch-sharp.svg b/_platforms/browser/www/svg/watch-sharp.svg new file mode 100644 index 000000000..05e9b2158 --- /dev/null +++ b/_platforms/browser/www/svg/watch-sharp.svg @@ -0,0 +1 @@ +Watch \ No newline at end of file diff --git a/_platforms/browser/www/svg/watch.svg b/_platforms/browser/www/svg/watch.svg new file mode 100644 index 000000000..f7ad227c4 --- /dev/null +++ b/_platforms/browser/www/svg/watch.svg @@ -0,0 +1 @@ +Watch \ No newline at end of file diff --git a/_platforms/browser/www/svg/water-outline.svg b/_platforms/browser/www/svg/water-outline.svg new file mode 100644 index 000000000..f15535bed --- /dev/null +++ b/_platforms/browser/www/svg/water-outline.svg @@ -0,0 +1 @@ +Water \ No newline at end of file diff --git a/_platforms/browser/www/svg/water-sharp.svg b/_platforms/browser/www/svg/water-sharp.svg new file mode 100644 index 000000000..6b9907224 --- /dev/null +++ b/_platforms/browser/www/svg/water-sharp.svg @@ -0,0 +1 @@ +Water \ No newline at end of file diff --git a/_platforms/browser/www/svg/water.svg b/_platforms/browser/www/svg/water.svg new file mode 100644 index 000000000..2aa9d8287 --- /dev/null +++ b/_platforms/browser/www/svg/water.svg @@ -0,0 +1 @@ +Water \ No newline at end of file diff --git a/_platforms/browser/www/svg/wifi-outline.svg b/_platforms/browser/www/svg/wifi-outline.svg new file mode 100644 index 000000000..0169b1541 --- /dev/null +++ b/_platforms/browser/www/svg/wifi-outline.svg @@ -0,0 +1 @@ +Wifi \ No newline at end of file diff --git a/_platforms/browser/www/svg/wifi-sharp.svg b/_platforms/browser/www/svg/wifi-sharp.svg new file mode 100644 index 000000000..a545cd25a --- /dev/null +++ b/_platforms/browser/www/svg/wifi-sharp.svg @@ -0,0 +1 @@ +Wifi \ No newline at end of file diff --git a/_platforms/browser/www/svg/wifi.svg b/_platforms/browser/www/svg/wifi.svg new file mode 100644 index 000000000..635b18847 --- /dev/null +++ b/_platforms/browser/www/svg/wifi.svg @@ -0,0 +1 @@ +Wifi \ No newline at end of file diff --git a/_platforms/browser/www/svg/wine-outline.svg b/_platforms/browser/www/svg/wine-outline.svg new file mode 100644 index 000000000..f465a2cd9 --- /dev/null +++ b/_platforms/browser/www/svg/wine-outline.svg @@ -0,0 +1 @@ +Wine \ No newline at end of file diff --git a/_platforms/browser/www/svg/wine-sharp.svg b/_platforms/browser/www/svg/wine-sharp.svg new file mode 100644 index 000000000..c02224855 --- /dev/null +++ b/_platforms/browser/www/svg/wine-sharp.svg @@ -0,0 +1 @@ +Wine \ No newline at end of file diff --git a/_platforms/browser/www/svg/wine.svg b/_platforms/browser/www/svg/wine.svg new file mode 100644 index 000000000..0b5fe14bb --- /dev/null +++ b/_platforms/browser/www/svg/wine.svg @@ -0,0 +1 @@ +Wine \ No newline at end of file diff --git a/_platforms/browser/www/svg/woman-outline.svg b/_platforms/browser/www/svg/woman-outline.svg new file mode 100644 index 000000000..dad5ddc40 --- /dev/null +++ b/_platforms/browser/www/svg/woman-outline.svg @@ -0,0 +1 @@ +Woman \ No newline at end of file diff --git a/_platforms/browser/www/svg/woman-sharp.svg b/_platforms/browser/www/svg/woman-sharp.svg new file mode 100644 index 000000000..42a7f8992 --- /dev/null +++ b/_platforms/browser/www/svg/woman-sharp.svg @@ -0,0 +1 @@ +Woman \ No newline at end of file diff --git a/_platforms/browser/www/svg/woman.svg b/_platforms/browser/www/svg/woman.svg new file mode 100644 index 000000000..cc3ec61b8 --- /dev/null +++ b/_platforms/browser/www/svg/woman.svg @@ -0,0 +1 @@ +Woman \ No newline at end of file diff --git a/_platforms/ios/.gitignore b/_platforms/ios/.gitignore new file mode 100644 index 000000000..cc76483f6 --- /dev/null +++ b/_platforms/ios/.gitignore @@ -0,0 +1,5 @@ +*.mode1v3 +*.perspectivev3 +*.pbxuser +.DS_Store +build/ diff --git a/_platforms/ios/CordovaLib/Classes/Private/CDVDebug.h b/_platforms/ios/CordovaLib/Classes/Private/CDVDebug.h new file mode 100644 index 000000000..4a0d9f929 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/CDVDebug.h @@ -0,0 +1,25 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#ifdef DEBUG + #define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) +#else + #define DLog(...) +#endif +#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) diff --git a/_platforms/ios/CordovaLib/Classes/Private/CDVJSON_private.h b/_platforms/ios/CordovaLib/Classes/Private/CDVJSON_private.h new file mode 100644 index 000000000..afb5cc661 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/CDVJSON_private.h @@ -0,0 +1,31 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +@interface NSArray (CDVJSONSerializingPrivate) +- (NSString*)cdv_JSONString; +@end + +@interface NSDictionary (CDVJSONSerializingPrivate) +- (NSString*)cdv_JSONString; +@end + +@interface NSString (CDVJSONSerializingPrivate) +- (id)cdv_JSONObject; +- (id)cdv_JSONFragment; +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/CDVJSON_private.m b/_platforms/ios/CordovaLib/Classes/Private/CDVJSON_private.m new file mode 100644 index 000000000..175ed3984 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/CDVJSON_private.m @@ -0,0 +1,99 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVJSON_private.h" +#import + +@implementation NSArray (CDVJSONSerializingPrivate) + +- (NSString*)cdv_JSONString +{ + @autoreleasepool { + NSError* error = nil; + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:self + options:0 + error:&error]; + + if (error != nil) { + NSLog(@"NSArray JSONString error: %@", [error localizedDescription]); + return nil; + } else { + return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } + } +} + +@end + +@implementation NSDictionary (CDVJSONSerializingPrivate) + +- (NSString*)cdv_JSONString +{ + @autoreleasepool { + NSError* error = nil; + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:self + options:NSJSONWritingPrettyPrinted + error:&error]; + + if (error != nil) { + NSLog(@"NSDictionary JSONString error: %@", [error localizedDescription]); + return nil; + } else { + return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } + } +} + +@end + +@implementation NSString (CDVJSONSerializingPrivate) + +- (id)cdv_JSONObject +{ + @autoreleasepool { + NSError* error = nil; + id object = [NSJSONSerialization JSONObjectWithData:[self dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingMutableContainers + error:&error]; + + if (error != nil) { + NSLog(@"NSString JSONObject error: %@, Malformed Data: %@", [error localizedDescription], self); + } + + return object; + } +} + +- (id)cdv_JSONFragment +{ + @autoreleasepool { + NSError* error = nil; + id object = [NSJSONSerialization JSONObjectWithData:[self dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingAllowFragments + error:&error]; + + if (error != nil) { + NSLog(@"NSString JSONObject error: %@", [error localizedDescription]); + } + + return object; + } +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/CDVPlugin+Private.h b/_platforms/ios/CordovaLib/Classes/Private/CDVPlugin+Private.h new file mode 100644 index 000000000..f88638c20 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/CDVPlugin+Private.h @@ -0,0 +1,24 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +@interface CDVPlugin (Private) + +- (instancetype)initWithWebViewEngine:(id )theWebViewEngine; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVGestureHandler/CDVGestureHandler.h b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVGestureHandler/CDVGestureHandler.h new file mode 100644 index 000000000..f39e066a2 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVGestureHandler/CDVGestureHandler.h @@ -0,0 +1,26 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface CDVGestureHandler : CDVPlugin + +@property (nonatomic, strong) UILongPressGestureRecognizer* lpgr; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVGestureHandler/CDVGestureHandler.m b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVGestureHandler/CDVGestureHandler.m new file mode 100644 index 000000000..34ed46339 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVGestureHandler/CDVGestureHandler.m @@ -0,0 +1,70 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVGestureHandler.h" + +@implementation CDVGestureHandler + +- (void)pluginInitialize +{ + [self applyLongPressFix]; +} + +- (void)applyLongPressFix +{ + // You can't suppress 3D Touch and still have regular longpress, + // so if this is false, let's not consider the 3D Touch setting at all. + if (![self.commandDelegate.settings objectForKey:@"suppresseslongpressgesture"] || + ![[self.commandDelegate.settings objectForKey:@"suppresseslongpressgesture"] boolValue]) { + return; + } + + self.lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGestures:)]; + self.lpgr.minimumPressDuration = 0.45f; + self.lpgr.allowableMovement = 200.0f; + + // 0.45 is ok for 'regular longpress', 0.05-0.08 is required for '3D Touch longpress', + // but since this will also kill onclick handlers (not ontouchend) it's optional. + if ([self.commandDelegate.settings objectForKey:@"suppresses3dtouchgesture"] && + [[self.commandDelegate.settings objectForKey:@"suppresses3dtouchgesture"] boolValue]) { + self.lpgr.minimumPressDuration = 0.15f; + } + + NSArray *views = self.webView.subviews; + if (views.count == 0) { + NSLog(@"No webview subviews found, not applying the longpress fix."); + return; + } + for (int i=0; i + +@interface CDVHandleOpenURL : CDVPlugin + +@property (nonatomic, strong) NSURL* url; +@property (nonatomic, assign) BOOL pageLoaded; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVHandleOpenURL/CDVHandleOpenURL.m b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVHandleOpenURL/CDVHandleOpenURL.m new file mode 100644 index 000000000..400cb9dea --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVHandleOpenURL/CDVHandleOpenURL.m @@ -0,0 +1,86 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVHandleOpenURL.h" +#import + +@implementation CDVHandleOpenURL + +- (void)pluginInitialize +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationLaunchedWithUrl:) name:CDVPluginHandleOpenURLNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationPageDidLoad:) name:CDVPageDidLoadNotification object:nil]; +} + +- (void)applicationLaunchedWithUrl:(NSNotification*)notification +{ + NSURL* url = [notification object]; + + self.url = url; + + // warm-start handler + if (self.pageLoaded) { + [self processOpenUrl:self.url pageLoaded:YES]; + self.url = nil; + } +} + +- (void)applicationPageDidLoad:(NSNotification*)notification +{ + // cold-start handler + + self.pageLoaded = YES; + + if (self.url) { + [self processOpenUrl:self.url pageLoaded:YES]; + self.url = nil; + } +} + +- (void)processOpenUrl:(NSURL*)url pageLoaded:(BOOL)pageLoaded +{ + __weak __typeof(self) weakSelf = self; + + dispatch_block_t handleOpenUrl = ^(void) { + // calls into javascript global function 'handleOpenURL' + NSString* jsString = [NSString stringWithFormat:@"document.addEventListener('deviceready',function(){if (typeof handleOpenURL === 'function') { handleOpenURL(\"%@\");}});", url.absoluteString]; + + [weakSelf.webViewEngine evaluateJavaScript:jsString completionHandler:nil]; + }; + + if (!pageLoaded) { + NSString* jsString = @"document.readystate"; + [self.webViewEngine evaluateJavaScript:jsString + completionHandler:^(id object, NSError* error) { + if ((error == nil) && [object isKindOfClass:[NSString class]]) { + NSString* readyState = (NSString*)object; + BOOL ready = [readyState isEqualToString:@"loaded"] || [readyState isEqualToString:@"complete"]; + if (ready) { + handleOpenUrl(); + } else { + self.url = url; + } + } + }]; + } else { + handleOpenUrl(); + } +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.h b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.h new file mode 100644 index 000000000..14c6a66d3 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.h @@ -0,0 +1,36 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import + +#define CDVWebViewNavigationType int + +typedef NS_ENUM(NSInteger, CDVIntentAndNavigationFilterValue) { + CDVIntentAndNavigationFilterValueIntentAllowed, + CDVIntentAndNavigationFilterValueNavigationAllowed, + CDVIntentAndNavigationFilterValueNoneAllowed +}; + +@interface CDVIntentAndNavigationFilter : CDVPlugin + ++ (CDVIntentAndNavigationFilterValue) filterUrl:(NSURL*)url intentsWhitelist:(CDVWhitelist*)intentsWhitelist navigationsWhitelist:(CDVWhitelist*)navigationsWhitelist; ++ (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest*)request navigationType:(CDVWebViewNavigationType)navigationType filterValue:(CDVIntentAndNavigationFilterValue)filterValue; ++ (BOOL)shouldOpenURLRequest:(NSURLRequest*)request navigationType:(CDVWebViewNavigationType)navigationType; +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.m b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.m new file mode 100644 index 000000000..3021f8f57 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.m @@ -0,0 +1,153 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVIntentAndNavigationFilter.h" +#import + +@interface CDVIntentAndNavigationFilter () + +@property (nonatomic, readwrite) NSMutableArray* allowIntents; +@property (nonatomic, readwrite) NSMutableArray* allowNavigations; +@property (nonatomic, readwrite) CDVWhitelist* allowIntentsWhitelist; +@property (nonatomic, readwrite) CDVWhitelist* allowNavigationsWhitelist; + +@end + +@implementation CDVIntentAndNavigationFilter + +#pragma mark NSXMLParserDelegate + +- (void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName attributes:(NSDictionary*)attributeDict +{ + if ([elementName isEqualToString:@"allow-navigation"]) { + [self.allowNavigations addObject:attributeDict[@"href"]]; + } + if ([elementName isEqualToString:@"allow-intent"]) { + [self.allowIntents addObject:attributeDict[@"href"]]; + } +} + +- (void)parserDidStartDocument:(NSXMLParser*)parser +{ + // file: url are added by default + // navigation to the scheme used by the app is also allowed + self.allowNavigations = [[NSMutableArray alloc] initWithArray:@[ @"file://"]]; + + // If the custom app scheme is defined, append it to the allow navigation as default + NSString* scheme = ((CDVViewController*)self.viewController).appScheme; + if (scheme) { + [self.allowNavigations addObject: [NSString stringWithFormat:@"%@://", scheme]]; + } + + // no intents are added by default + self.allowIntents = [[NSMutableArray alloc] init]; +} + +- (void)parserDidEndDocument:(NSXMLParser*)parser +{ + self.allowIntentsWhitelist = [[CDVWhitelist alloc] initWithArray:self.allowIntents]; + self.allowNavigationsWhitelist = [[CDVWhitelist alloc] initWithArray:self.allowNavigations]; +} + +- (void)parser:(NSXMLParser*)parser parseErrorOccurred:(NSError*)parseError +{ + NSAssert(NO, @"config.xml parse error line %ld col %ld", (long)[parser lineNumber], (long)[parser columnNumber]); +} + +#pragma mark CDVPlugin + +- (void)pluginInitialize +{ + if ([self.viewController isKindOfClass:[CDVViewController class]]) { + [(CDVViewController*)self.viewController parseSettingsWithParser:self]; + } +} + ++ (CDVIntentAndNavigationFilterValue) filterUrl:(NSURL*)url intentsWhitelist:(CDVWhitelist*)intentsWhitelist navigationsWhitelist:(CDVWhitelist*)navigationsWhitelist +{ + // a URL can only allow-intent OR allow-navigation, if both are specified, + // only allow-navigation is allowed + + BOOL allowNavigationsPass = [navigationsWhitelist URLIsAllowed:url logFailure:NO]; + BOOL allowIntentPass = [intentsWhitelist URLIsAllowed:url logFailure:NO]; + + if (allowNavigationsPass && allowIntentPass) { + return CDVIntentAndNavigationFilterValueNavigationAllowed; + } else if (allowNavigationsPass) { + return CDVIntentAndNavigationFilterValueNavigationAllowed; + } else if (allowIntentPass) { + return CDVIntentAndNavigationFilterValueIntentAllowed; + } + + return CDVIntentAndNavigationFilterValueNoneAllowed; +} + +- (CDVIntentAndNavigationFilterValue) filterUrl:(NSURL*)url +{ + return [[self class] filterUrl:url intentsWhitelist:self.allowIntentsWhitelist navigationsWhitelist:self.allowNavigationsWhitelist]; +} + +#define CDVWebViewNavigationTypeLinkClicked 0 +#define CDVWebViewNavigationTypeLinkOther -1 + ++ (BOOL)shouldOpenURLRequest:(NSURLRequest*)request navigationType:(CDVWebViewNavigationType)navigationType +{ + BOOL isMainNavigation = [[request.mainDocumentURL absoluteString] isEqualToString:[request.URL absoluteString]]; + + return ( + navigationType == CDVWebViewNavigationTypeLinkClicked || + (navigationType == CDVWebViewNavigationTypeLinkOther && isMainNavigation) + ); +} + ++ (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest*)request navigationType:(CDVWebViewNavigationType)navigationType filterValue:(CDVIntentAndNavigationFilterValue)filterValue +{ + NSString* allowIntents_whitelistRejectionFormatString = @"ERROR External navigation rejected - not set for url='%@'"; + NSString* allowNavigations_whitelistRejectionFormatString = @"ERROR Internal navigation rejected - not set for url='%@'"; + + NSURL* url = [request URL]; + + switch (filterValue) { + case CDVIntentAndNavigationFilterValueNavigationAllowed: + return YES; + case CDVIntentAndNavigationFilterValueIntentAllowed: + // only allow-intent if it's a CDVWebViewNavigationTypeLinkClicked (anchor tag) or CDVWebViewNavigationTypeOther and it's an internal link + if ([[self class] shouldOpenURLRequest:request navigationType:navigationType]){ + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + } + + // consume the request (i.e. no error) if it wasn't handled above + return NO; + case CDVIntentAndNavigationFilterValueNoneAllowed: + // allow-navigation attempt failed for sure + NSLog(@"%@", [NSString stringWithFormat:allowNavigations_whitelistRejectionFormatString, [url absoluteString]]); + // anchor tag link means it was an allow-intent attempt that failed as well + if (CDVWebViewNavigationTypeLinkClicked == navigationType) { + NSLog(@"%@", [NSString stringWithFormat:allowIntents_whitelistRejectionFormatString, [url absoluteString]]); + } + return NO; + } +} + +- (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest*)request navigationType:(CDVWebViewNavigationType)navigationType +{ + return [[self class] shouldOverrideLoadWithRequest:request navigationType:navigationType filterValue:[self filterUrl:request.URL]]; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLaunchScreen/CDVLaunchScreen.h b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLaunchScreen/CDVLaunchScreen.h new file mode 100644 index 000000000..e973e373e --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLaunchScreen/CDVLaunchScreen.h @@ -0,0 +1,27 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface CDVLaunchScreen : CDVPlugin + +- (void)show:(CDVInvokedUrlCommand*)command; +- (void)hide:(CDVInvokedUrlCommand*)command; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLaunchScreen/CDVLaunchScreen.m b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLaunchScreen/CDVLaunchScreen.m new file mode 100644 index 000000000..9ff062af3 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLaunchScreen/CDVLaunchScreen.m @@ -0,0 +1,39 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVLaunchScreen.h" +#import + +@implementation CDVLaunchScreen + +- (void)show:(CDVInvokedUrlCommand*)command +{ + if ([self.viewController isKindOfClass:[CDVViewController class]]) { + [(CDVViewController*)self.viewController showLaunchScreen:YES]; + } +} + +- (void)hide:(CDVInvokedUrlCommand*)command +{ + if ([self.viewController isKindOfClass:[CDVViewController class]]) { + [(CDVViewController*)self.viewController showLaunchScreen:NO]; + } +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLogger/CDVLogger.h b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLogger/CDVLogger.h new file mode 100644 index 000000000..7cfb3063e --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLogger/CDVLogger.h @@ -0,0 +1,26 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface CDVLogger : CDVPlugin + +- (void)logLevel:(CDVInvokedUrlCommand*)command; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLogger/CDVLogger.m b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLogger/CDVLogger.m new file mode 100644 index 000000000..810caa560 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVLogger/CDVLogger.m @@ -0,0 +1,37 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVLogger.h" + +@implementation CDVLogger + +/* log a message */ +- (void)logLevel:(CDVInvokedUrlCommand*)command +{ + id level = [command argumentAtIndex:0]; + id message = [command argumentAtIndex:1]; + + if ([level isEqualToString:@"LOG"]) { + NSLog(@"%@", message); + } else { + NSLog(@"%@: %@", level, message); + } +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.h b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.h new file mode 100644 index 000000000..47e26d1e3 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.h @@ -0,0 +1,29 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import + +@interface CDVWebViewEngine : CDVPlugin + +@property (nonatomic, strong, readonly) id uiDelegate; + +- (void)allowsBackForwardNavigationGestures:(CDVInvokedUrlCommand*)command; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m new file mode 100644 index 000000000..a07bf290a --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m @@ -0,0 +1,607 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVWebViewEngine.h" +#import "CDVWebViewUIDelegate.h" +#import "CDVWebViewProcessPoolFactory.h" +#import +#import "CDVURLSchemeHandler.h" + +#import + +#define CDV_BRIDGE_NAME @"cordova" +#define CDV_WKWEBVIEW_FILE_URL_LOAD_SELECTOR @"loadFileURL:allowingReadAccessToURL:" + +@interface CDVWebViewWeakScriptMessageHandler : NSObject + +@property (nonatomic, weak, readonly) idscriptMessageHandler; + +- (instancetype)initWithScriptMessageHandler:(id)scriptMessageHandler; + +@end + + +@interface CDVWebViewEngine () + +@property (nonatomic, strong, readwrite) UIView* engineWebView; +@property (nonatomic, strong, readwrite) id uiDelegate; +@property (nonatomic, weak) id weakScriptMessageHandler; +@property (nonatomic, strong) CDVURLSchemeHandler * schemeHandler; +@property (nonatomic, readwrite) NSString *CDV_ASSETS_URL; +@property (nonatomic, readwrite) Boolean cdvIsFileScheme; + +@end + +// see forwardingTargetForSelector: selector comment for the reason for this pragma +#pragma clang diagnostic ignored "-Wprotocol" + +@implementation CDVWebViewEngine + +@synthesize engineWebView = _engineWebView; + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super init]; + if (self) { + if (NSClassFromString(@"WKWebView") == nil) { + return nil; + } + + self.engineWebView = [[WKWebView alloc] initWithFrame:frame]; + } + + return self; +} + +- (WKWebViewConfiguration*) createConfigurationFromSettings:(NSDictionary*)settings +{ + WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init]; + configuration.processPool = [[CDVWebViewProcessPoolFactory sharedFactory] sharedProcessPool]; + if (settings == nil) { + return configuration; + } + + configuration.allowsInlineMediaPlayback = [settings cordovaBoolSettingForKey:@"AllowInlineMediaPlayback" defaultValue:NO]; + + // Set the media types that are required for user action for playback + WKAudiovisualMediaTypes mediaType = WKAudiovisualMediaTypeAll; // default + + // targetMediaType will always exist, either from user's "config.xml" or default ("defaults.xml"). + id targetMediaType = [settings cordovaSettingForKey:@"MediaTypesRequiringUserActionForPlayback"]; + if ([targetMediaType isEqualToString:@"none"]) { + mediaType = WKAudiovisualMediaTypeNone; + } else if ([targetMediaType isEqualToString:@"audio"]) { + mediaType = WKAudiovisualMediaTypeAudio; + } else if ([targetMediaType isEqualToString:@"video"]) { + mediaType = WKAudiovisualMediaTypeVideo; + } else if ([targetMediaType isEqualToString:@"all"]) { + mediaType = WKAudiovisualMediaTypeAll; + } else { + NSLog(@"Invalid \"MediaTypesRequiringUserActionForPlayback\" was detected. Fallback to default value of \"all\" types."); + } + configuration.mediaTypesRequiringUserActionForPlayback = mediaType; + + configuration.suppressesIncrementalRendering = [settings cordovaBoolSettingForKey:@"SuppressesIncrementalRendering" defaultValue:NO]; + + /* + * If the old preference key "MediaPlaybackAllowsAirPlay" exists, use it or default to "YES". + * Check if the new preference key "AllowsAirPlayForMediaPlayback" exists and overwrite the "MediaPlaybackAllowsAirPlay" value. + */ + BOOL allowsAirPlayForMediaPlayback = [settings cordovaBoolSettingForKey:@"MediaPlaybackAllowsAirPlay" defaultValue:YES]; + if([settings cordovaSettingForKey:@"AllowsAirPlayForMediaPlayback"] != nil) { + allowsAirPlayForMediaPlayback = [settings cordovaBoolSettingForKey:@"AllowsAirPlayForMediaPlayback" defaultValue:YES]; + } + configuration.allowsAirPlayForMediaPlayback = allowsAirPlayForMediaPlayback; + + /* + * Sets Custom User Agents + * - (Default) "userAgent" is set the the clean user agent. + * E.g. + * UserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" + * + * - If "OverrideUserAgent" is set, it will overwrite the entire "userAgent" value. The "AppendUserAgent" will be iggnored if set. + * Notice: The override logic is handled in the "pluginInitialize" method. + * E.g. + * OverrideUserAgent = "foobar" + * UserAgent = "foobar" + * + * - If "AppendUserAgent" is set and "OverrideUserAgent" is not set, the user defined "AppendUserAgent" will be appended to the "userAgent" + * E.g. + * AppendUserAgent = "foobar" + * UserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 foobar" + */ + NSString *userAgent = configuration.applicationNameForUserAgent; + if ( + [settings cordovaSettingForKey:@"OverrideUserAgent"] == nil && + [settings cordovaSettingForKey:@"AppendUserAgent"] != nil + ) { + userAgent = [NSString stringWithFormat:@"%@ %@", userAgent, [settings cordovaSettingForKey:@"AppendUserAgent"]]; + } + configuration.applicationNameForUserAgent = userAgent; + + if (@available(iOS 13.0, *)) { + NSString *contentMode = [settings cordovaSettingForKey:@"PreferredContentMode"]; + if ([contentMode isEqual: @"mobile"]) { + configuration.defaultWebpagePreferences.preferredContentMode = WKContentModeMobile; + } else if ([contentMode isEqual: @"desktop"]) { + configuration.defaultWebpagePreferences.preferredContentMode = WKContentModeDesktop; + } + + } + + return configuration; +} + +- (void)pluginInitialize +{ + // viewController would be available now. we attempt to set all possible delegates to it, by default + CDVViewController* vc = (CDVViewController*)self.viewController; + NSDictionary* settings = self.commandDelegate.settings; + + NSString *scheme = [settings cordovaSettingForKey:@"scheme"]; + + // If scheme is file or nil, then default to file scheme + self.cdvIsFileScheme = [scheme isEqualToString: @"file"] || scheme == nil; + + NSString *hostname = @""; + if(!self.cdvIsFileScheme) { + if(scheme == nil || [WKWebView handlesURLScheme:scheme]){ + scheme = @"app"; + } + vc.appScheme = scheme; + + hostname = [settings cordovaSettingForKey:@"hostname"]; + if(hostname == nil){ + hostname = @"localhost"; + } + + self.CDV_ASSETS_URL = [NSString stringWithFormat:@"%@://%@", scheme, hostname]; + } + + CDVWebViewUIDelegate* uiDelegate = [[CDVWebViewUIDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]]; + uiDelegate.allowNewWindows = [settings cordovaBoolSettingForKey:@"AllowNewWindows" defaultValue:NO]; + self.uiDelegate = uiDelegate; + + CDVWebViewWeakScriptMessageHandler *weakScriptMessageHandler = [[CDVWebViewWeakScriptMessageHandler alloc] initWithScriptMessageHandler:self]; + + WKUserContentController* userContentController = [[WKUserContentController alloc] init]; + [userContentController addScriptMessageHandler:weakScriptMessageHandler name:CDV_BRIDGE_NAME]; + + if(self.CDV_ASSETS_URL) { + NSString *scriptCode = [NSString stringWithFormat:@"window.CDV_ASSETS_URL = '%@';", self.CDV_ASSETS_URL]; + WKUserScript *wkScript = [[WKUserScript alloc] initWithSource:scriptCode injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; + + if (wkScript) { + [userContentController addUserScript:wkScript]; + } + } + + WKWebViewConfiguration* configuration = [self createConfigurationFromSettings:settings]; + configuration.userContentController = userContentController; + + // Do not configure the scheme handler if the scheme is default (file) + if(!self.cdvIsFileScheme) { + self.schemeHandler = [[CDVURLSchemeHandler alloc] initWithVC:vc]; + [configuration setURLSchemeHandler:self.schemeHandler forURLScheme:scheme]; + } + + // re-create WKWebView, since we need to update configuration + WKWebView* wkWebView = [[WKWebView alloc] initWithFrame:self.engineWebView.frame configuration:configuration]; + wkWebView.UIDelegate = self.uiDelegate; + + /* + * This is where the "OverrideUserAgent" is handled. This will replace the entire UserAgent + * with the user defined custom UserAgent. + */ + if ([settings cordovaSettingForKey:@"OverrideUserAgent"] != nil) { + wkWebView.customUserAgent = [settings cordovaSettingForKey:@"OverrideUserAgent"]; + } + + self.engineWebView = wkWebView; + + if ([self.viewController conformsToProtocol:@protocol(WKUIDelegate)]) { + wkWebView.UIDelegate = (id )self.viewController; + } + + if ([self.viewController conformsToProtocol:@protocol(WKNavigationDelegate)]) { + wkWebView.navigationDelegate = (id )self.viewController; + } else { + wkWebView.navigationDelegate = (id )self; + } + + if ([self.viewController conformsToProtocol:@protocol(WKScriptMessageHandler)]) { + [wkWebView.configuration.userContentController addScriptMessageHandler:(id < WKScriptMessageHandler >)self.viewController name:CDV_BRIDGE_NAME]; + } + + [self updateSettings:settings]; + + // check if content thread has died on resume + NSLog(@"%@", @"CDVWebViewEngine will reload WKWebView if required on resume"); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onAppWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification object:nil]; + + NSLog(@"Using WKWebView"); + + [self addURLObserver]; +} + +- (void)onReset { + [self addURLObserver]; +} + +static void * KVOContext = &KVOContext; + +- (void)addURLObserver { + if(!IsAtLeastiOSVersion(@"9.0")){ + [self.webView addObserver:self forKeyPath:@"URL" options:0 context:KVOContext]; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (context == KVOContext) { + if (object == [self webView] && [keyPath isEqualToString: @"URL"] && [object valueForKeyPath:keyPath] == nil){ + NSLog(@"URL is nil. Reloading WKWebView"); + [(WKWebView*)_engineWebView reload]; + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +- (void) onAppWillEnterForeground:(NSNotification*)notification { + if ([self shouldReloadWebView]) { + NSLog(@"%@", @"CDVWebViewEngine reloading!"); + [(WKWebView*)_engineWebView reload]; + } +} + +- (BOOL)shouldReloadWebView +{ + WKWebView* wkWebView = (WKWebView*)_engineWebView; + return [self shouldReloadWebView:wkWebView.URL title:wkWebView.title]; +} + +- (BOOL)shouldReloadWebView:(NSURL*)location title:(NSString*)title +{ + BOOL title_is_nil = (title == nil); + BOOL location_is_blank = [[location absoluteString] isEqualToString:@"about:blank"]; + + BOOL reload = (title_is_nil || location_is_blank); + +#ifdef DEBUG + NSLog(@"%@", @"CDVWebViewEngine shouldReloadWebView::"); + NSLog(@"CDVWebViewEngine shouldReloadWebView title: %@", title); + NSLog(@"CDVWebViewEngine shouldReloadWebView location: %@", [location absoluteString]); + NSLog(@"CDVWebViewEngine shouldReloadWebView reload: %u", reload); +#endif + + return reload; +} + + +- (id)loadRequest:(NSURLRequest*)request +{ + if ([self canLoadRequest:request]) { // can load, differentiate between file urls and other schemes + if(request.URL.fileURL && self.cdvIsFileScheme) { + NSURL* readAccessUrl = [request.URL URLByDeletingLastPathComponent]; + return [(WKWebView*)_engineWebView loadFileURL:request.URL allowingReadAccessToURL:readAccessUrl]; + } else if (request.URL.fileURL) { + NSURL* startURL = [NSURL URLWithString:((CDVViewController *)self.viewController).startPage]; + NSString* startFilePath = [self.commandDelegate pathForResource:[startURL path]]; + NSURL *url = [[NSURL URLWithString:self.CDV_ASSETS_URL] URLByAppendingPathComponent:request.URL.path]; + if ([request.URL.path isEqualToString:startFilePath]) { + url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", self.CDV_ASSETS_URL, startURL]]; + } + if(request.URL.query) { + url = [NSURL URLWithString:[@"?" stringByAppendingString:request.URL.query] relativeToURL:url]; + } + if(request.URL.fragment) { + url = [NSURL URLWithString:[@"#" stringByAppendingString:request.URL.fragment] relativeToURL:url]; + } + request = [NSURLRequest requestWithURL:url]; + } + return [(WKWebView*)_engineWebView loadRequest:request]; + } else { // can't load, print out error + NSString* errorHtml = [NSString stringWithFormat: + @"" + @"Error" + @"
" + @"

The WebView engine '%@' is unable to load the request: %@

" + @"

Most likely the cause of the error is that the loading of file urls is not supported in iOS %@.

" + @"
", + NSStringFromClass([self class]), + [request.URL description], + [[UIDevice currentDevice] systemVersion] + ]; + return [self loadHTMLString:errorHtml baseURL:nil]; + } +} + +- (id)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL +{ + return [(WKWebView*)_engineWebView loadHTMLString:string baseURL:baseURL]; +} + +- (NSURL*) URL +{ + return [(WKWebView*)_engineWebView URL]; +} + +- (BOOL) canLoadRequest:(NSURLRequest*)request +{ + // See: https://issues.apache.org/jira/browse/CB-9636 + SEL wk_sel = NSSelectorFromString(CDV_WKWEBVIEW_FILE_URL_LOAD_SELECTOR); + + // if it's a file URL, check whether WKWebView has the selector (which is in iOS 9 and up only) + if (request.URL.fileURL) { + return [_engineWebView respondsToSelector:wk_sel]; + } else { + return YES; + } +} + +- (void)updateSettings:(NSDictionary*)settings +{ + WKWebView* wkWebView = (WKWebView*)_engineWebView; + + wkWebView.configuration.preferences.minimumFontSize = [settings cordovaFloatSettingForKey:@"MinimumFontSize" defaultValue:0.0]; + + /* + wkWebView.configuration.preferences.javaScriptEnabled = [settings cordovaBoolSettingForKey:@"JavaScriptEnabled" default:YES]; + wkWebView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = [settings cordovaBoolSettingForKey:@"JavaScriptCanOpenWindowsAutomatically" default:NO]; + */ + + // By default, DisallowOverscroll is false (thus bounce is allowed) + BOOL bounceAllowed = !([settings cordovaBoolSettingForKey:@"DisallowOverscroll" defaultValue:NO]); + + // prevent webView from bouncing + if (!bounceAllowed) { + if ([wkWebView respondsToSelector:@selector(scrollView)]) { + ((UIScrollView*)[wkWebView scrollView]).bounces = NO; + } else { + for (id subview in wkWebView.subviews) { + if ([[subview class] isSubclassOfClass:[UIScrollView class]]) { + ((UIScrollView*)subview).bounces = NO; + } + } + } + } + + NSString* decelerationSetting = [settings cordovaSettingForKey:@"WKWebViewDecelerationSpeed"]; + + if (![@"fast" isEqualToString:decelerationSetting]) { + [wkWebView.scrollView setDecelerationRate:UIScrollViewDecelerationRateNormal]; + } else { + [wkWebView.scrollView setDecelerationRate:UIScrollViewDecelerationRateFast]; + } + + wkWebView.allowsBackForwardNavigationGestures = [settings cordovaBoolSettingForKey:@"AllowBackForwardNavigationGestures" defaultValue:NO]; + wkWebView.allowsLinkPreview = [settings cordovaBoolSettingForKey:@"Allow3DTouchLinkPreview" defaultValue:YES]; +} + +- (void)updateWithInfo:(NSDictionary*)info +{ + NSDictionary* scriptMessageHandlers = [info objectForKey:kCDVWebViewEngineScriptMessageHandlers]; + NSDictionary* settings = [info objectForKey:kCDVWebViewEngineWebViewPreferences]; + id navigationDelegate = [info objectForKey:kCDVWebViewEngineWKNavigationDelegate]; + id uiDelegate = [info objectForKey:kCDVWebViewEngineWKUIDelegate]; + + WKWebView* wkWebView = (WKWebView*)_engineWebView; + + if (scriptMessageHandlers && [scriptMessageHandlers isKindOfClass:[NSDictionary class]]) { + NSArray* allKeys = [scriptMessageHandlers allKeys]; + + for (NSString* key in allKeys) { + id object = [scriptMessageHandlers objectForKey:key]; + if ([object conformsToProtocol:@protocol(WKScriptMessageHandler)]) { + [wkWebView.configuration.userContentController addScriptMessageHandler:object name:key]; + } + } + } + + if (navigationDelegate && [navigationDelegate conformsToProtocol:@protocol(WKNavigationDelegate)]) { + wkWebView.navigationDelegate = navigationDelegate; + } + + if (uiDelegate && [uiDelegate conformsToProtocol:@protocol(WKUIDelegate)]) { + wkWebView.UIDelegate = uiDelegate; + } + + if (settings && [settings isKindOfClass:[NSDictionary class]]) { + [self updateSettings:settings]; + } +} + +// This forwards the methods that are in the header that are not implemented here. +// Both WKWebView implement the below: +// loadHTMLString:baseURL: +// loadRequest: +- (id)forwardingTargetForSelector:(SEL)aSelector +{ + return _engineWebView; +} + +- (UIView*)webView +{ + return self.engineWebView; +} + +#pragma mark WKScriptMessageHandler implementation + +- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message +{ + if (![message.name isEqualToString:CDV_BRIDGE_NAME]) { + return; + } + + CDVViewController* vc = (CDVViewController*)self.viewController; + + NSArray* jsonEntry = message.body; // NSString:callbackId, NSString:service, NSString:action, NSArray:args + CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry]; + CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName); + + if (![vc.commandQueue execute:command]) { +#ifdef DEBUG + NSError* error = nil; + NSString* commandJson = nil; + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonEntry + options:0 + error:&error]; + + if (error == nil) { + commandJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } + + static NSUInteger maxLogLength = 1024; + NSString* commandString = ([commandJson length] > maxLogLength) ? + [NSString stringWithFormat : @"%@[...]", [commandJson substringToIndex:maxLogLength]] : + commandJson; + + NSLog(@"FAILED pluginJSON = %@", commandString); +#endif + } +} + +#pragma mark WKNavigationDelegate implementation + +- (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(WKNavigation*)navigation +{ + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:webView]]; +} + +- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation +{ + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:webView]]; +} + +- (void)webView:(WKWebView*)theWebView didFailProvisionalNavigation:(WKNavigation*)navigation withError:(NSError*)error +{ + [self webView:theWebView didFailNavigation:navigation withError:error]; +} + +- (void)webView:(WKWebView*)theWebView didFailNavigation:(WKNavigation*)navigation withError:(NSError*)error +{ + CDVViewController* vc = (CDVViewController*)self.viewController; + + NSString* message = [NSString stringWithFormat:@"Failed to load webpage with error: %@", [error localizedDescription]]; + NSLog(@"%@", message); + + NSURL* errorUrl = vc.errorURL; + if (errorUrl) { + NSCharacterSet *charSet = [NSCharacterSet URLFragmentAllowedCharacterSet]; + errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [message stringByAddingPercentEncodingWithAllowedCharacters:charSet]] relativeToURL:errorUrl]; + NSLog(@"%@", [errorUrl absoluteString]); + [theWebView loadRequest:[NSURLRequest requestWithURL:errorUrl]]; + } +} + +- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView +{ + [webView reload]; +} + +- (BOOL)defaultResourcePolicyForURL:(NSURL*)url +{ + // all file:// urls are allowed + if ([url isFileURL]) { + return YES; + } + + return NO; +} + +- (void) webView: (WKWebView *) webView decidePolicyForNavigationAction: (WKNavigationAction*) navigationAction decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler +{ + NSURL* url = [navigationAction.request URL]; + CDVViewController* vc = (CDVViewController*)self.viewController; + + /* + * Give plugins the chance to handle the url + */ + BOOL anyPluginsResponded = NO; + BOOL shouldAllowRequest = NO; + + for (NSString* pluginName in vc.pluginObjects) { + CDVPlugin* plugin = [vc.pluginObjects objectForKey:pluginName]; + SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:"); + if ([plugin respondsToSelector:selector]) { + anyPluginsResponded = YES; + // https://issues.apache.org/jira/browse/CB-12497 + int navType = (int)navigationAction.navigationType; + shouldAllowRequest = (((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, navigationAction.request, navType)); + if (!shouldAllowRequest) { + break; + } + } + } + + if (anyPluginsResponded) { + return decisionHandler(shouldAllowRequest); + } + + /* + * Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview. + */ + BOOL shouldAllowNavigation = [self defaultResourcePolicyForURL:url]; + if (shouldAllowNavigation) { + return decisionHandler(YES); + } else { + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]]; + } + + return decisionHandler(NO); +} + +#pragma mark - Plugin interface + +- (void)allowsBackForwardNavigationGestures:(CDVInvokedUrlCommand*)command; +{ + id value = [command argumentAtIndex:0]; + if (!([value isKindOfClass:[NSNumber class]])) { + value = [NSNumber numberWithBool:NO]; + } + + WKWebView* wkWebView = (WKWebView*)_engineWebView; + wkWebView.allowsBackForwardNavigationGestures = [value boolValue]; +} + +@end + +#pragma mark - CDVWebViewWeakScriptMessageHandler + +@implementation CDVWebViewWeakScriptMessageHandler + +- (instancetype)initWithScriptMessageHandler:(id)scriptMessageHandler +{ + self = [super init]; + if (self) { + _scriptMessageHandler = scriptMessageHandler; + } + return self; +} + +- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message +{ + [self.scriptMessageHandler userContentController:userContentController didReceiveScriptMessage:message]; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewUIDelegate.h b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewUIDelegate.h new file mode 100644 index 000000000..8ae39b67c --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewUIDelegate.h @@ -0,0 +1,32 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface CDVWebViewUIDelegate : NSObject +{ + NSMutableArray* windows; +} + +@property (nonatomic, copy) NSString* title; +@property (nonatomic, assign) BOOL allowNewWindows; + +- (instancetype)initWithTitle:(NSString*)title; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewUIDelegate.m b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewUIDelegate.m new file mode 100644 index 000000000..784af8dfa --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewUIDelegate.m @@ -0,0 +1,163 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVWebViewUIDelegate.h" + +@implementation CDVWebViewUIDelegate + +- (instancetype)initWithTitle:(NSString*)title +{ + self = [super init]; + if (self) { + self.title = title; + windows = [[NSMutableArray alloc] init]; + } + + return self; +} + +- (void) webView:(WKWebView*)webView runJavaScriptAlertPanelWithMessage:(NSString*)message + initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(void))completionHandler +{ + UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + + [alert addAction:ok]; + + UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; + + [rootController presentViewController:alert animated:YES completion:nil]; +} + +- (void) webView:(WKWebView*)webView runJavaScriptConfirmPanelWithMessage:(NSString*)message + initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(BOOL result))completionHandler +{ + UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(YES); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + + [alert addAction:ok]; + + UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(NO); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + [alert addAction:cancel]; + + UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; + + [rootController presentViewController:alert animated:YES completion:nil]; +} + +- (void) webView:(WKWebView*)webView runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt + defaultText:(NSString*)defaultText initiatedByFrame:(WKFrameInfo*)frame + completionHandler:(void (^)(NSString* result))completionHandler +{ + UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title + message:prompt + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(((UITextField*)alert.textFields[0]).text); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + + [alert addAction:ok]; + + UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(nil); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + [alert addAction:cancel]; + + [alert addTextFieldWithConfigurationHandler:^(UITextField* textField) { + textField.text = defaultText; + }]; + + UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; + + [rootController presentViewController:alert animated:YES completion:nil]; +} + +- (WKWebView*) webView:(WKWebView*)webView createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration forNavigationAction:(WKNavigationAction*)navigationAction windowFeatures:(WKWindowFeatures*)windowFeatures +{ + if (!navigationAction.targetFrame.isMainFrame) { + if (self.allowNewWindows) { + WKWebView* v = [[WKWebView alloc] initWithFrame:webView.frame configuration:configuration]; + v.UIDelegate = webView.UIDelegate; + v.navigationDelegate = webView.navigationDelegate; + + UIViewController* vc = [[UIViewController alloc] init]; + vc.modalPresentationStyle = UIModalPresentationOverCurrentContext; + vc.view = v; + + [windows addObject:vc]; + + UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; + [rootController presentViewController:vc animated:YES completion:nil]; + return v; + } else { + [webView loadRequest:navigationAction.request]; + } + } + + return nil; +} + +- (void)webViewDidClose:(WKWebView*)webView +{ + for (UIViewController* vc in windows) { + if (vc.view == webView) { + [vc dismissViewControllerAnimated:YES completion:nil]; + [windows removeObject:vc]; + break; + } + } + + // We do not allow closing the primary WebView +} + + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDV.h b/_platforms/ios/CordovaLib/Classes/Public/CDV.h new file mode 100644 index 000000000..8706ef4aa --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDV.h @@ -0,0 +1,30 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVAvailability.h" +#import "CDVAvailabilityDeprecated.h" +#import "CDVAppDelegate.h" +#import "CDVPlugin.h" +#import "CDVPluginResult.h" +#import "CDVViewController.h" +#import "CDVCommandDelegate.h" +#import "CDVInvokedUrlCommand.h" +#import "CDVWhitelist.h" +#import "CDVScreenOrientationDelegate.h" +#import "CDVTimer.h" diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVAppDelegate.h b/_platforms/ios/CordovaLib/Classes/Public/CDVAppDelegate.h new file mode 100644 index 000000000..de5b518f8 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVAppDelegate.h @@ -0,0 +1,28 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import "CDVViewController.h" + +@interface CDVAppDelegate : NSObject {} + +@property (nonatomic, strong) IBOutlet UIWindow* window; +@property (nonatomic, strong) IBOutlet CDVViewController* viewController; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVAppDelegate.m b/_platforms/ios/CordovaLib/Classes/Public/CDVAppDelegate.m new file mode 100644 index 000000000..9188be79e --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVAppDelegate.m @@ -0,0 +1,97 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVAppDelegate.h" + +@implementation CDVAppDelegate + +@synthesize window, viewController; + +- (id)init +{ + self = [super init]; + return self; +} + +#pragma mark UIApplicationDelegate implementation + +/** + * This is main kick off after the app inits, the views and Settings are setup here. (preferred - iOS4 and up) + */ +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions +{ + CGRect screenBounds = [[UIScreen mainScreen] bounds]; + + self.window = [[UIWindow alloc] initWithFrame:screenBounds]; + self.window.autoresizesSubviews = YES; + + // only set if not already set in subclass + if (self.viewController == nil) { + self.viewController = [[CDVViewController alloc] init]; + } + + // Set your app's start page by setting the tag in config.xml. + // If necessary, uncomment the line below to override it. + // self.viewController.startPage = @"index.html"; + + // NOTE: To customize the view's frame size (which defaults to full screen), override + // [self.viewController viewWillAppear:] in your view controller. + + self.window.rootViewController = self.viewController; + [self.window makeKeyAndVisible]; + + return YES; +} + +// this happens while we are running ( in the background, or from within our own app ) +// only valid if 40x-Info.plist specifies a protocol to handle +- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options +{ + if (!url) { + return NO; + } + + NSMutableDictionary * openURLData = [[NSMutableDictionary alloc] init]; + + [openURLData setValue:url forKey:@"url"]; + + if (options[UIApplicationOpenURLOptionsSourceApplicationKey]) { + [openURLData setValue:options[UIApplicationOpenURLOptionsSourceApplicationKey] forKey:@"sourceApplication"]; + } + + if (options[UIApplicationOpenURLOptionsAnnotationKey]) { + [openURLData setValue:options[UIApplicationOpenURLOptionsAnnotationKey] forKey:@"annotation"]; + } + + // all plugins will get the notification, and their handlers will be called + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLWithAppSourceAndAnnotationNotification object:openURLData]]; + + return YES; +} + +- (UIInterfaceOrientationMask)application:(UIApplication*)application supportedInterfaceOrientationsForWindow:(UIWindow*)window +{ + // iPhone doesn't support upside down by default, while the iPad does. Override to allow all orientations always, and let the root view controller decide what's allowed (the supported orientations mask gets intersected). + NSUInteger supportedInterfaceOrientations = (1 << UIInterfaceOrientationPortrait) | (1 << UIInterfaceOrientationLandscapeLeft) | (1 << UIInterfaceOrientationLandscapeRight) | (1 << UIInterfaceOrientationPortraitUpsideDown); + + return supportedInterfaceOrientations; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVAvailability.h b/_platforms/ios/CordovaLib/Classes/Public/CDVAvailability.h new file mode 100644 index 000000000..8b1ab479b --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVAvailability.h @@ -0,0 +1,118 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVAvailabilityDeprecated.h" + +#define __CORDOVA_IOS__ + +#define __CORDOVA_0_9_6 906 +#define __CORDOVA_1_0_0 10000 +#define __CORDOVA_1_1_0 10100 +#define __CORDOVA_1_2_0 10200 +#define __CORDOVA_1_3_0 10300 +#define __CORDOVA_1_4_0 10400 +#define __CORDOVA_1_4_1 10401 +#define __CORDOVA_1_5_0 10500 +#define __CORDOVA_1_6_0 10600 +#define __CORDOVA_1_6_1 10601 +#define __CORDOVA_1_7_0 10700 +#define __CORDOVA_1_8_0 10800 +#define __CORDOVA_1_8_1 10801 +#define __CORDOVA_1_9_0 10900 +#define __CORDOVA_2_0_0 20000 +#define __CORDOVA_2_1_0 20100 +#define __CORDOVA_2_2_0 20200 +#define __CORDOVA_2_3_0 20300 +#define __CORDOVA_2_4_0 20400 +#define __CORDOVA_2_5_0 20500 +#define __CORDOVA_2_6_0 20600 +#define __CORDOVA_2_7_0 20700 +#define __CORDOVA_2_8_0 20800 +#define __CORDOVA_2_9_0 20900 +#define __CORDOVA_3_0_0 30000 +#define __CORDOVA_3_1_0 30100 +#define __CORDOVA_3_2_0 30200 +#define __CORDOVA_3_3_0 30300 +#define __CORDOVA_3_4_0 30400 +#define __CORDOVA_3_4_1 30401 +#define __CORDOVA_3_5_0 30500 +#define __CORDOVA_3_6_0 30600 +#define __CORDOVA_3_7_0 30700 +#define __CORDOVA_3_8_0 30800 +#define __CORDOVA_3_9_0 30900 +#define __CORDOVA_3_9_1 30901 +#define __CORDOVA_3_9_2 30902 +#define __CORDOVA_4_0_0 40000 +#define __CORDOVA_4_0_1 40001 +#define __CORDOVA_4_1_0 40100 +#define __CORDOVA_4_1_1 40101 +#define __CORDOVA_4_2_0 40200 +#define __CORDOVA_4_2_1 40201 +#define __CORDOVA_4_3_0 40300 +#define __CORDOVA_4_3_1 40301 +#define __CORDOVA_4_4_0 40400 +#define __CORDOVA_4_5_0 40500 +#define __CORDOVA_4_5_1 40501 +#define __CORDOVA_4_5_2 40502 +#define __CORDOVA_4_5_4 40504 +#define __CORDOVA_5_0_0 50000 +#define __CORDOVA_5_0_1 50001 +#define __CORDOVA_5_1_0 50100 +#define __CORDOVA_5_1_1 50101 +#define __CORDOVA_6_0_0 60000 +#define __CORDOVA_6_1_0 60100 +#define __CORDOVA_6_2_0 60200 +/* coho:next-version,insert-before */ +#define __CORDOVA_NA 99999 /* not available */ + +/* + #if CORDOVA_VERSION_MIN_REQUIRED >= __CORDOVA_4_0_0 + // do something when its at least 4.0.0 + #else + // do something else (non 4.0.0) + #endif + */ +#ifndef CORDOVA_VERSION_MIN_REQUIRED + /* coho:next-version-min-required,replace-after */ + #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_6_2_0 +#endif + +/* + Returns YES if it is at least version specified as NSString(X) + Usage: + if (IsAtLeastiOSVersion(@"5.1")) { + // do something for iOS 5.1 or greater + } + */ +#define IsAtLeastiOSVersion(X) ([[[UIDevice currentDevice] systemVersion] compare:X options:NSNumericSearch] != NSOrderedAscending) + +/* Return the string version of the decimal version */ +#define CDV_VERSION [NSString stringWithFormat:@"%d.%d.%d", \ + (CORDOVA_VERSION_MIN_REQUIRED / 10000), \ + (CORDOVA_VERSION_MIN_REQUIRED % 10000) / 100, \ + (CORDOVA_VERSION_MIN_REQUIRED % 10000) % 100] + +// Enable this to log all exec() calls. +#define CDV_ENABLE_EXEC_LOGGING 0 +#if CDV_ENABLE_EXEC_LOGGING + #define CDV_EXEC_LOG NSLog +#else + #define CDV_EXEC_LOG(...) do { \ +} while (NO) +#endif diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVAvailabilityDeprecated.h b/_platforms/ios/CordovaLib/Classes/Public/CDVAvailabilityDeprecated.h new file mode 100644 index 000000000..abf7a16ad --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVAvailabilityDeprecated.h @@ -0,0 +1,26 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +#ifdef __clang__ + #define CDV_DEPRECATED(version, msg) __attribute__((deprecated("Deprecated in Cordova " #version ". " msg))) +#else + #define CDV_DEPRECATED(version, msg) __attribute__((deprecated())) +#endif diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVCommandDelegate.h b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandDelegate.h new file mode 100644 index 000000000..ac82727a9 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandDelegate.h @@ -0,0 +1,49 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVAvailability.h" +#import "CDVInvokedUrlCommand.h" + +@class CDVPlugin; +@class CDVPluginResult; +@class CDVWhitelist; + +typedef NSURL* (^ UrlTransformerBlock)(NSURL*); + +@protocol CDVCommandDelegate + +@property (nonatomic, readonly) NSDictionary* settings; +@property (nonatomic, copy) UrlTransformerBlock urlTransformer; + +- (NSString*)pathForResource:(NSString*)resourcepath; +- (id)getCommandInstance:(NSString*)pluginName; + +// Sends a plugin result to the JS. This is thread-safe. +- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId; +// Evaluates the given JS. This is thread-safe. +- (void)evalJs:(NSString*)js; +// Can be used to evaluate JS right away instead of scheduling it on the run-loop. +// This is required for dispatch resign and pause events, but should not be used +// without reason. Without the run-loop delay, alerts used in JS callbacks may result +// in dead-lock. This method must be called from the UI thread. +- (void)evalJs:(NSString*)js scheduledOnRunLoop:(BOOL)scheduledOnRunLoop; +// Runs the given block on a background thread using a shared thread-pool. +- (void)runInBackground:(void (^)(void))block; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVCommandDelegateImpl.h b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandDelegateImpl.h new file mode 100644 index 000000000..053113431 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandDelegateImpl.h @@ -0,0 +1,36 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import "CDVCommandDelegate.h" + +@class CDVViewController; +@class CDVCommandQueue; + +@interface CDVCommandDelegateImpl : NSObject { + @private + __weak CDVViewController* _viewController; + NSRegularExpression* _callbackIdPattern; + @protected + __weak CDVCommandQueue* _commandQueue; + BOOL _delayResponses; +} +- (id)initWithViewController:(CDVViewController*)viewController; +- (void)flushCommandQueueWithDelayedJs; +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVCommandDelegateImpl.m b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandDelegateImpl.m new file mode 100644 index 000000000..649d30dbb --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandDelegateImpl.m @@ -0,0 +1,181 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVCommandDelegateImpl.h" +#import "CDVJSON_private.h" +#import "CDVCommandQueue.h" +#import "CDVPluginResult.h" +#import "CDVViewController.h" + +@implementation CDVCommandDelegateImpl + +@synthesize urlTransformer; + +- (id)initWithViewController:(CDVViewController*)viewController +{ + self = [super init]; + if (self != nil) { + _viewController = viewController; + _commandQueue = _viewController.commandQueue; + + NSError* err = nil; + _callbackIdPattern = [NSRegularExpression regularExpressionWithPattern:@"[^A-Za-z0-9._-]" options:0 error:&err]; + if (err != nil) { + // Couldn't initialize Regex + NSLog(@"Error: Couldn't initialize regex"); + _callbackIdPattern = nil; + } + } + return self; +} + +- (NSString*)pathForResource:(NSString*)resourcepath +{ + NSBundle* mainBundle = [NSBundle mainBundle]; + NSMutableArray* directoryParts = [NSMutableArray arrayWithArray:[resourcepath componentsSeparatedByString:@"/"]]; + NSString* filename = [directoryParts lastObject]; + + [directoryParts removeLastObject]; + + NSString* directoryPartsJoined = [directoryParts componentsJoinedByString:@"/"]; + NSString* directoryStr = _viewController.wwwFolderName; + + if ([directoryPartsJoined length] > 0) { + directoryStr = [NSString stringWithFormat:@"%@/%@", _viewController.wwwFolderName, [directoryParts componentsJoinedByString:@"/"]]; + } + + return [mainBundle pathForResource:filename ofType:@"" inDirectory:directoryStr]; +} + +- (void)flushCommandQueueWithDelayedJs +{ + _delayResponses = YES; + [_commandQueue executePending]; + _delayResponses = NO; +} + +- (void)evalJsHelper2:(NSString*)js +{ + CDV_EXEC_LOG(@"Exec: evalling: %@", [js substringToIndex:MIN([js length], 160)]); + [_viewController.webViewEngine evaluateJavaScript:js completionHandler:^(id obj, NSError* error) { + // TODO: obj can be something other than string + if ([obj isKindOfClass:[NSString class]]) { + NSString* commandsJSON = (NSString*)obj; + if ([commandsJSON length] > 0) { + CDV_EXEC_LOG(@"Exec: Retrieved new exec messages by chaining."); + } + + [self->_commandQueue enqueueCommandBatch:commandsJSON]; + [self->_commandQueue executePending]; + } + }]; +} + +- (void)evalJsHelper:(NSString*)js +{ + // Cycle the run-loop before executing the JS. + // For _delayResponses - + // This ensures that we don't eval JS during the middle of an existing JS + // function (possible since WKWebViewDelegate callbacks can be synchronous). + // For !isMainThread - + // It's a hard error to eval on the non-UI thread. + // For !_commandQueue.currentlyExecuting - + // This works around a bug where sometimes alerts() within callbacks can cause + // dead-lock. + // If the commandQueue is currently executing, then we know that it is safe to + // execute the callback immediately. + // Using (dispatch_get_main_queue()) does *not* fix deadlocks for some reason, + // but performSelectorOnMainThread: does. + if (_delayResponses || ![NSThread isMainThread] || !_commandQueue.currentlyExecuting) { + [self performSelectorOnMainThread:@selector(evalJsHelper2:) withObject:js waitUntilDone:NO]; + } else { + [self evalJsHelper2:js]; + } +} + +- (BOOL)isValidCallbackId:(NSString*)callbackId +{ + if ((callbackId == nil) || (_callbackIdPattern == nil)) { + return NO; + } + + // Disallow if too long or if any invalid characters were found. + if (([callbackId length] > 100) || [_callbackIdPattern firstMatchInString:callbackId options:0 range:NSMakeRange(0, [callbackId length])]) { + return NO; + } + return YES; +} + +- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId +{ + CDV_EXEC_LOG(@"Exec(%@): Sending result. Status=%@", callbackId, result.status); + // This occurs when there is are no win/fail callbacks for the call. + if ([@"INVALID" isEqualToString:callbackId]) { + return; + } + // This occurs when the callback id is malformed. + if (![self isValidCallbackId:callbackId]) { + NSLog(@"Invalid callback id received by sendPluginResult"); + return; + } + int status = [result.status intValue]; + BOOL keepCallback = [result.keepCallback boolValue]; + NSString* argumentsAsJSON = [result argumentsAsJSON]; + BOOL debug = NO; + +#ifdef DEBUG + debug = YES; +#endif + + NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d, %d)", callbackId, status, argumentsAsJSON, keepCallback, debug]; + + [self evalJsHelper:js]; +} + +- (void)evalJs:(NSString*)js +{ + [self evalJs:js scheduledOnRunLoop:YES]; +} + +- (void)evalJs:(NSString*)js scheduledOnRunLoop:(BOOL)scheduledOnRunLoop +{ + js = [NSString stringWithFormat:@"try{cordova.require('cordova/exec').nativeEvalAndFetch(function(){%@})}catch(e){console.log('exception nativeEvalAndFetch : '+e);};", js]; + if (scheduledOnRunLoop) { + [self evalJsHelper:js]; + } else { + [self evalJsHelper2:js]; + } +} + +- (id)getCommandInstance:(NSString*)pluginName +{ + return [_viewController getCommandInstance:pluginName]; +} + +- (void)runInBackground:(void (^)(void))block +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); +} + +- (NSDictionary*)settings +{ + return _viewController.settings; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVCommandQueue.h b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandQueue.h new file mode 100644 index 000000000..cb7bd6e4b --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandQueue.h @@ -0,0 +1,39 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@class CDVInvokedUrlCommand; +@class CDVViewController; + +@interface CDVCommandQueue : NSObject + +@property (nonatomic, readonly) BOOL currentlyExecuting; + +- (id)initWithViewController:(CDVViewController*)viewController; +- (void)dispose; + +- (void)resetRequestId; +- (void)enqueueCommandBatch:(NSString*)batchJSON; + +- (void)fetchCommandsFromJs; +- (void)executePending; +- (BOOL)execute:(CDVInvokedUrlCommand*)command; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVCommandQueue.m b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandQueue.m new file mode 100644 index 000000000..b78ed833e --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVCommandQueue.m @@ -0,0 +1,194 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#include +#import "CDVCommandQueue.h" +#import "CDVViewController.h" +#import "CDVCommandDelegateImpl.h" +#import "CDVJSON_private.h" +#import "CDVDebug.h" + +// Parse JS on the main thread if it's shorter than this. +static const NSInteger JSON_SIZE_FOR_MAIN_THREAD = 4 * 1024; // Chosen arbitrarily. +// Execute multiple commands in one go until this many seconds have passed. +static const double MAX_EXECUTION_TIME = .008; // Half of a 60fps frame. + +@interface CDVCommandQueue () { + NSInteger _lastCommandQueueFlushRequestId; + __weak CDVViewController* _viewController; + NSMutableArray* _queue; + NSTimeInterval _startExecutionTime; +} +@end + +@implementation CDVCommandQueue + +- (BOOL)currentlyExecuting +{ + return _startExecutionTime > 0; +} + +- (id)initWithViewController:(CDVViewController*)viewController +{ + self = [super init]; + if (self != nil) { + _viewController = viewController; + _queue = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)dispose +{ + // TODO(agrieve): Make this a zeroing weak ref once we drop support for 4.3. + _viewController = nil; +} + +- (void)resetRequestId +{ + _lastCommandQueueFlushRequestId = 0; +} + +- (void)enqueueCommandBatch:(NSString*)batchJSON +{ + if ([batchJSON length] > 0) { + NSMutableArray* commandBatchHolder = [[NSMutableArray alloc] init]; + [_queue addObject:commandBatchHolder]; + if ([batchJSON length] < JSON_SIZE_FOR_MAIN_THREAD) { + [commandBatchHolder addObject:[batchJSON cdv_JSONObject]]; + } else { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() { + NSMutableArray* result = [batchJSON cdv_JSONObject]; + @synchronized(commandBatchHolder) { + [commandBatchHolder addObject:result]; + } + [self performSelectorOnMainThread:@selector(executePending) withObject:nil waitUntilDone:NO]; + }); + } + } +} + +- (void)fetchCommandsFromJs +{ + __weak CDVCommandQueue* weakSelf = self; + NSString* js = @"cordova.require('cordova/exec').nativeFetchMessages()"; + + [_viewController.webViewEngine evaluateJavaScript:js + completionHandler:^(id obj, NSError* error) { + if ((error == nil) && [obj isKindOfClass:[NSString class]]) { + NSString* queuedCommandsJSON = (NSString*)obj; + CDV_EXEC_LOG(@"Exec: Flushed JS->native queue (hadCommands=%d).", [queuedCommandsJSON length] > 0); + [weakSelf enqueueCommandBatch:queuedCommandsJSON]; + // this has to be called here now, because fetchCommandsFromJs is now async (previously: synchronous) + [self executePending]; + } + }]; +} + +- (void)executePending +{ + // Make us re-entrant-safe. + if (_startExecutionTime > 0) { + return; + } + @try { + _startExecutionTime = [NSDate timeIntervalSinceReferenceDate]; + + while ([_queue count] > 0) { + NSMutableArray* commandBatchHolder = _queue[0]; + NSMutableArray* commandBatch = nil; + @synchronized(commandBatchHolder) { + // If the next-up command is still being decoded, wait for it. + if ([commandBatchHolder count] == 0) { + break; + } + commandBatch = commandBatchHolder[0]; + } + + while ([commandBatch count] > 0) { + @autoreleasepool { + // Execute the commands one-at-a-time. + NSArray* jsonEntry = [commandBatch cdv_dequeue]; + if ([commandBatch count] == 0) { + [_queue removeObjectAtIndex:0]; + } + CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry]; + CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName); + + if (![self execute:command]) { +#ifdef DEBUG + NSString* commandJson = [jsonEntry cdv_JSONString]; + static NSUInteger maxLogLength = 1024; + NSString* commandString = ([commandJson length] > maxLogLength) ? + [NSString stringWithFormat : @"%@[...]", [commandJson substringToIndex:maxLogLength]] : + commandJson; + + DLog(@"FAILED pluginJSON = %@", commandString); +#endif + } + } + + // Yield if we're taking too long. + if (([_queue count] > 0) && ([NSDate timeIntervalSinceReferenceDate] - _startExecutionTime > MAX_EXECUTION_TIME)) { + [self performSelector:@selector(executePending) withObject:nil afterDelay:0]; + return; + } + } + } + } @finally + { + _startExecutionTime = 0; + } +} + +- (BOOL)execute:(CDVInvokedUrlCommand*)command +{ + if ((command.className == nil) || (command.methodName == nil)) { + NSLog(@"ERROR: Classname and/or methodName not found for command."); + return NO; + } + + // Fetch an instance of this class + CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className]; + + if (!([obj isKindOfClass:[CDVPlugin class]])) { + NSLog(@"ERROR: Plugin '%@' not found, or is not a CDVPlugin. Check your plugin mapping in config.xml.", command.className); + return NO; + } + BOOL retVal = YES; + double started = [[NSDate date] timeIntervalSince1970] * 1000.0; + // Find the proper selector to call. + NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName]; + SEL normalSelector = NSSelectorFromString(methodName); + if ([obj respondsToSelector:normalSelector]) { + // [obj performSelector:normalSelector withObject:command]; + ((void (*)(id, SEL, id))objc_msgSend)(obj, normalSelector, command); + } else { + // There's no method to call, so throw an error. + NSLog(@"ERROR: Method '%@' not defined in Plugin '%@'", methodName, command.className); + retVal = NO; + } + double elapsed = [[NSDate date] timeIntervalSince1970] * 1000.0 - started; + if (elapsed > 10) { + NSLog(@"THREAD WARNING: ['%@'] took '%f' ms. Plugin should use a background thread.", command.className, elapsed); + } + return retVal; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVConfigParser.h b/_platforms/ios/CordovaLib/Classes/Public/CDVConfigParser.h new file mode 100644 index 000000000..bae3d0f8b --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVConfigParser.h @@ -0,0 +1,30 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +@interface CDVConfigParser : NSObject +{ + NSString* featureName; +} + +@property (nonatomic, readonly, strong) NSMutableDictionary* pluginsDict; +@property (nonatomic, readonly, strong) NSMutableDictionary* settings; +@property (nonatomic, readonly, strong) NSMutableArray* startupPluginNames; +@property (nonatomic, readonly, strong) NSString* startPage; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVConfigParser.m b/_platforms/ios/CordovaLib/Classes/Public/CDVConfigParser.m new file mode 100644 index 000000000..ab32b4a76 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVConfigParser.m @@ -0,0 +1,81 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVConfigParser.h" + +@interface CDVConfigParser () + +@property (nonatomic, readwrite, strong) NSMutableDictionary* pluginsDict; +@property (nonatomic, readwrite, strong) NSMutableDictionary* settings; +@property (nonatomic, readwrite, strong) NSMutableArray* startupPluginNames; +@property (nonatomic, readwrite, strong) NSString* startPage; + +@end + +@implementation CDVConfigParser + +@synthesize pluginsDict, settings, startPage, startupPluginNames; + +- (id)init +{ + self = [super init]; + if (self != nil) { + self.pluginsDict = [[NSMutableDictionary alloc] initWithCapacity:30]; + self.settings = [[NSMutableDictionary alloc] initWithCapacity:30]; + self.startupPluginNames = [[NSMutableArray alloc] initWithCapacity:8]; + featureName = nil; + } + return self; +} + +- (void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName attributes:(NSDictionary*)attributeDict +{ + if ([elementName isEqualToString:@"preference"]) { + settings[[attributeDict[@"name"] lowercaseString]] = attributeDict[@"value"]; + } else if ([elementName isEqualToString:@"feature"]) { // store feature name to use with correct parameter set + featureName = [attributeDict[@"name"] lowercaseString]; + } else if ((featureName != nil) && [elementName isEqualToString:@"param"]) { + NSString* paramName = [attributeDict[@"name"] lowercaseString]; + id value = attributeDict[@"value"]; + if ([paramName isEqualToString:@"ios-package"]) { + pluginsDict[featureName] = value; + } + BOOL paramIsOnload = ([paramName isEqualToString:@"onload"] && [@"true" isEqualToString : value]); + BOOL attribIsOnload = [@"true" isEqualToString :[attributeDict[@"onload"] lowercaseString]]; + if (paramIsOnload || attribIsOnload) { + [self.startupPluginNames addObject:featureName]; + } + } else if ([elementName isEqualToString:@"content"]) { + self.startPage = attributeDict[@"src"]; + } +} + +- (void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName +{ + if ([elementName isEqualToString:@"feature"]) { // no longer handling a feature so release + featureName = nil; + } +} + +- (void)parser:(NSXMLParser*)parser parseErrorOccurred:(NSError*)parseError +{ + NSAssert(NO, @"config.xml parse error line %ld col %ld", (long)[parser lineNumber], (long)[parser columnNumber]); +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVInvokedUrlCommand.h b/_platforms/ios/CordovaLib/Classes/Public/CDVInvokedUrlCommand.h new file mode 100644 index 000000000..993e0a285 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVInvokedUrlCommand.h @@ -0,0 +1,52 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface CDVInvokedUrlCommand : NSObject { + NSString* _callbackId; + NSString* _className; + NSString* _methodName; + NSArray* _arguments; +} + +@property (nonatomic, readonly) NSArray* arguments; +@property (nonatomic, readonly) NSString* callbackId; +@property (nonatomic, readonly) NSString* className; +@property (nonatomic, readonly) NSString* methodName; + ++ (CDVInvokedUrlCommand*)commandFromJson:(NSArray*)jsonEntry; + +- (id)initWithArguments:(NSArray*)arguments + callbackId:(NSString*)callbackId + className:(NSString*)className + methodName:(NSString*)methodName; + +- (id)initFromJson:(NSArray*)jsonEntry; + +// Returns the argument at the given index. +// If index >= the number of arguments, returns nil. +// If the argument at the given index is NSNull, returns nil. +- (id)argumentAtIndex:(NSUInteger)index; +// Same as above, but returns defaultValue instead of nil. +- (id)argumentAtIndex:(NSUInteger)index withDefault:(id)defaultValue; +// Same as above, but returns defaultValue instead of nil, and if the argument is not of the expected class, returns defaultValue +- (id)argumentAtIndex:(NSUInteger)index withDefault:(id)defaultValue andClass:(Class)aClass; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVInvokedUrlCommand.m b/_platforms/ios/CordovaLib/Classes/Public/CDVInvokedUrlCommand.m new file mode 100644 index 000000000..5b4281dfc --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVInvokedUrlCommand.m @@ -0,0 +1,116 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVInvokedUrlCommand.h" +#import "CDVJSON_private.h" + +@implementation CDVInvokedUrlCommand + +@synthesize arguments = _arguments; +@synthesize callbackId = _callbackId; +@synthesize className = _className; +@synthesize methodName = _methodName; + ++ (CDVInvokedUrlCommand*)commandFromJson:(NSArray*)jsonEntry +{ + return [[CDVInvokedUrlCommand alloc] initFromJson:jsonEntry]; +} + +- (id)initFromJson:(NSArray*)jsonEntry +{ + id tmp = [jsonEntry objectAtIndex:0]; + NSString* callbackId = tmp == [NSNull null] ? nil : tmp; + NSString* className = [jsonEntry objectAtIndex:1]; + NSString* methodName = [jsonEntry objectAtIndex:2]; + NSMutableArray* arguments = [jsonEntry objectAtIndex:3]; + + return [self initWithArguments:arguments + callbackId:callbackId + className:className + methodName:methodName]; +} + +- (id)initWithArguments:(NSArray*)arguments + callbackId:(NSString*)callbackId + className:(NSString*)className + methodName:(NSString*)methodName +{ + self = [super init]; + if (self != nil) { + _arguments = arguments; + _callbackId = callbackId; + _className = className; + _methodName = methodName; + } + [self massageArguments]; + return self; +} + +- (void)massageArguments +{ + NSMutableArray* newArgs = nil; + + for (NSUInteger i = 0, count = [_arguments count]; i < count; ++i) { + id arg = [_arguments objectAtIndex:i]; + if (![arg isKindOfClass:[NSDictionary class]]) { + continue; + } + NSDictionary* dict = arg; + NSString* type = [dict objectForKey:@"CDVType"]; + if (!type || ![type isEqualToString:@"ArrayBuffer"]) { + continue; + } + NSString* data = [dict objectForKey:@"data"]; + if (!data) { + continue; + } + if (newArgs == nil) { + newArgs = [NSMutableArray arrayWithArray:_arguments]; + _arguments = newArgs; + } + [newArgs replaceObjectAtIndex:i withObject:[[NSData alloc] initWithBase64EncodedString:data options:0]]; + } +} + +- (id)argumentAtIndex:(NSUInteger)index +{ + return [self argumentAtIndex:index withDefault:nil]; +} + +- (id)argumentAtIndex:(NSUInteger)index withDefault:(id)defaultValue +{ + return [self argumentAtIndex:index withDefault:defaultValue andClass:nil]; +} + +- (id)argumentAtIndex:(NSUInteger)index withDefault:(id)defaultValue andClass:(Class)aClass +{ + if (index >= [_arguments count]) { + return defaultValue; + } + id ret = [_arguments objectAtIndex:index]; + if (ret == [NSNull null]) { + ret = defaultValue; + } + if ((aClass != nil) && ![ret isKindOfClass:aClass]) { + ret = defaultValue; + } + return ret; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin+Resources.h b/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin+Resources.h new file mode 100644 index 000000000..cc43b16b7 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin+Resources.h @@ -0,0 +1,39 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import "CDVPlugin.h" + +@interface CDVPlugin (CDVPluginResources) + +/* + This will return the localized string for a key in a .bundle that is named the same as your class + For example, if your plugin class was called Foo, and you have a Spanish localized strings file, it will + try to load the desired key from Foo.bundle/es.lproj/Localizable.strings + */ +- (NSString*)pluginLocalizedString:(NSString*)key; + +/* + This will return the image for a name in a .bundle that is named the same as your class + For example, if your plugin class was called Foo, and you have an image called "bar", + it will try to load the image from Foo.bundle/bar.png (and appropriately named retina versions) + */ +- (UIImage*)pluginImageResource:(NSString*)name; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin+Resources.m b/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin+Resources.m new file mode 100644 index 000000000..56907385f --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin+Resources.m @@ -0,0 +1,38 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVPlugin+Resources.h" + +@implementation CDVPlugin (CDVPluginResources) + +- (NSString*)pluginLocalizedString:(NSString*)key +{ + NSBundle* bundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:NSStringFromClass([self class]) ofType:@"bundle"]]; + + return [bundle localizedStringForKey:(key) value:nil table:nil]; +} + +- (UIImage*)pluginImageResource:(NSString*)name +{ + NSString* resourceIdentifier = [NSString stringWithFormat:@"%@.bundle/%@", NSStringFromClass([self class]), name]; + + return [UIImage imageNamed:resourceIdentifier]; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin.h b/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin.h new file mode 100644 index 000000000..0bdbbabbb --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin.h @@ -0,0 +1,74 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import +#import "CDVPluginResult.h" +#import "NSMutableArray+QueueAdditions.h" +#import "CDVCommandDelegate.h" +#import "CDVWebViewEngineProtocol.h" + +@interface UIView (org_apache_cordova_UIView_Extension) + +@property (nonatomic, weak) UIScrollView* scrollView; + +@end + +extern NSString* const CDVPageDidLoadNotification; +extern NSString* const CDVPluginHandleOpenURLNotification; +extern NSString* const CDVPluginHandleOpenURLWithAppSourceAndAnnotationNotification; +extern NSString* const CDVPluginResetNotification; +extern NSString* const CDVViewWillAppearNotification; +extern NSString* const CDVViewDidAppearNotification; +extern NSString* const CDVViewWillDisappearNotification; +extern NSString* const CDVViewDidDisappearNotification; +extern NSString* const CDVViewWillLayoutSubviewsNotification; +extern NSString* const CDVViewDidLayoutSubviewsNotification; +extern NSString* const CDVViewWillTransitionToSizeNotification; + +@interface CDVPlugin : NSObject {} + +@property (nonatomic, readonly, weak) UIView* webView; +@property (nonatomic, readonly, weak) id webViewEngine; + +@property (nonatomic, weak) UIViewController* viewController; +@property (nonatomic, weak) id commandDelegate; + +@property (readonly, assign) BOOL hasPendingOperation; + +- (void)pluginInitialize; + +- (void)handleOpenURL:(NSNotification*)notification; +- (void)handleOpenURLWithApplicationSourceAndAnnotation:(NSNotification*)notification; +- (void)onAppTerminate; +- (void)onMemoryWarning; +- (void)onReset; +- (void)dispose; + +/* + // see initWithWebView implementation + - (void) onPause {} + - (void) onResume {} + - (void) onOrientationWillChange {} + - (void) onOrientationDidChange {} + */ + +- (id)appDelegate; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin.m b/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin.m new file mode 100644 index 000000000..6af03e9cc --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVPlugin.m @@ -0,0 +1,199 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVPlugin.h" +#import "CDVPlugin+Private.h" +#import "CDVPlugin+Resources.h" +#import "CDVViewController.h" +#include + +@implementation UIView (org_apache_cordova_UIView_Extension) + +@dynamic scrollView; + +- (UIScrollView*)scrollView +{ + SEL scrollViewSelector = NSSelectorFromString(@"scrollView"); + + if ([self respondsToSelector:scrollViewSelector]) { + return ((id (*)(id, SEL))objc_msgSend)(self, scrollViewSelector); + } + + return nil; +} + +@end + +NSString* const CDVPageDidLoadNotification = @"CDVPageDidLoadNotification"; +NSString* const CDVPluginHandleOpenURLNotification = @"CDVPluginHandleOpenURLNotification"; +NSString* const CDVPluginHandleOpenURLWithAppSourceAndAnnotationNotification = @"CDVPluginHandleOpenURLWithAppSourceAndAnnotationNotification"; +NSString* const CDVPluginResetNotification = @"CDVPluginResetNotification"; +NSString* const CDVViewWillAppearNotification = @"CDVViewWillAppearNotification"; +NSString* const CDVViewDidAppearNotification = @"CDVViewDidAppearNotification"; +NSString* const CDVViewWillDisappearNotification = @"CDVViewWillDisappearNotification"; +NSString* const CDVViewDidDisappearNotification = @"CDVViewDidDisappearNotification"; +NSString* const CDVViewWillLayoutSubviewsNotification = @"CDVViewWillLayoutSubviewsNotification"; +NSString* const CDVViewDidLayoutSubviewsNotification = @"CDVViewDidLayoutSubviewsNotification"; +NSString* const CDVViewWillTransitionToSizeNotification = @"CDVViewWillTransitionToSizeNotification"; + +@interface CDVPlugin () + +@property (readwrite, assign) BOOL hasPendingOperation; +@property (nonatomic, readwrite, weak) id webViewEngine; + +@end + +@implementation CDVPlugin +@synthesize webViewEngine, viewController, commandDelegate, hasPendingOperation; +@dynamic webView; + +// Do not override these methods. Use pluginInitialize instead. +- (instancetype)initWithWebViewEngine:(id )theWebViewEngine +{ + self = [super init]; + if (self) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppTerminate) name:UIApplicationWillTerminateNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleOpenURL:) name:CDVPluginHandleOpenURLNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleOpenURLWithApplicationSourceAndAnnotation:) name:CDVPluginHandleOpenURLWithAppSourceAndAnnotationNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReset) name:CDVPluginResetNotification object:theWebViewEngine.engineWebView]; + + self.webViewEngine = theWebViewEngine; + } + return self; +} + +- (void)pluginInitialize +{ + // You can listen to more app notifications, see: + // http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006728-CH3-DontLinkElementID_4 + + // NOTE: if you want to use these, make sure you uncomment the corresponding notification handler + + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume) name:UIApplicationWillEnterForegroundNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationWillChange) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationDidChange) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; + + // Added in 2.5.0 + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad:) name:CDVPageDidLoadNotification object:self.webView]; + //Added in 4.3.0 + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillAppear:) name:CDVViewWillAppearNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidAppear:) name:CDVViewDidAppearNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillDisappear:) name:CDVViewWillDisappearNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidDisappear:) name:CDVViewDidDisappearNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillLayoutSubviews:) name:CDVViewWillLayoutSubviewsNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidLayoutSubviews:) name:CDVViewDidLayoutSubviewsNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillTransitionToSize:) name:CDVViewWillTransitionToSizeNotification object:nil]; +} + +- (void)dispose +{ + viewController = nil; + commandDelegate = nil; +} + +- (UIView*)webView +{ + if (self.webViewEngine != nil) { + return self.webViewEngine.engineWebView; + } + + return nil; +} + +/* +// NOTE: for onPause and onResume, calls into JavaScript must not call or trigger any blocking UI, like alerts +- (void) onPause {} +- (void) onResume {} +- (void) onOrientationWillChange {} +- (void) onOrientationDidChange {} +*/ + +/* NOTE: calls into JavaScript must not call or trigger any blocking UI, like alerts */ +- (void)handleOpenURL:(NSNotification*)notification +{ + // override to handle urls sent to your app + // register your url schemes in your App-Info.plist + + NSURL* url = [notification object]; + + if ([url isKindOfClass:[NSURL class]]) { + /* Do your thing! */ + } +} + +/* + NOTE: calls into JavaScript must not call or trigger any blocking UI, like alerts + */ +- (void)handleOpenURLWithApplicationSourceAndAnnotation: (NSNotification*)notification +{ + + // override to handle urls sent to your app + // register your url schemes in your App-Info.plist + + // The notification object is an NSDictionary which contains + // - url which is a type of NSURL + // - sourceApplication which is a type of NSString and represents the package + // id of the app that calls our app + // - annotation which a type of Property list which can be several different types + // please see https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/PropertyList.html + + NSDictionary* notificationData = [notification object]; + + if ([notificationData isKindOfClass: NSDictionary.class]){ + + NSURL* url = notificationData[@"url"]; + NSString* sourceApplication = notificationData[@"sourceApplication"]; + id annotation = notificationData[@"annotation"]; + + if ([url isKindOfClass:NSURL.class] && [sourceApplication isKindOfClass:NSString.class] && annotation) { + /* Do your thing! */ + } + } +} + + +/* NOTE: calls into JavaScript must not call or trigger any blocking UI, like alerts */ +- (void)onAppTerminate +{ + // override this if you need to do any cleanup on app exit +} + +- (void)onMemoryWarning +{ + // override to remove caches, etc +} + +- (void)onReset +{ + // Override to cancel any long-running requests when the WebView navigates or refreshes. +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; // this will remove all notifications unless added using addObserverForName:object:queue:usingBlock: +} + +- (id)appDelegate +{ + return [[UIApplication sharedApplication] delegate]; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVPluginResult.h b/_platforms/ios/CordovaLib/Classes/Public/CDVPluginResult.h new file mode 100644 index 000000000..2f62d77b4 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVPluginResult.h @@ -0,0 +1,83 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import "CDVAvailability.h" + +typedef NS_ENUM(NSUInteger, CDVCommandStatus) { + CDVCommandStatus_NO_RESULT NS_SWIFT_NAME(noResult) = 0, + CDVCommandStatus_OK NS_SWIFT_NAME(ok), + CDVCommandStatus_CLASS_NOT_FOUND_EXCEPTION NS_SWIFT_NAME(classNotFoundException), + CDVCommandStatus_ILLEGAL_ACCESS_EXCEPTION NS_SWIFT_NAME(illegalAccessException), + CDVCommandStatus_INSTANTIATION_EXCEPTION NS_SWIFT_NAME(instantiationException), + CDVCommandStatus_MALFORMED_URL_EXCEPTION NS_SWIFT_NAME(malformedUrlException), + CDVCommandStatus_IO_EXCEPTION NS_SWIFT_NAME(ioException), + CDVCommandStatus_INVALID_ACTION NS_SWIFT_NAME(invalidAction), + CDVCommandStatus_JSON_EXCEPTION NS_SWIFT_NAME(jsonException), + CDVCommandStatus_ERROR NS_SWIFT_NAME(error) +}; + +// This exists to preserve compatibility with early Swift plugins, who are +// using CDVCommandStatus as ObjC-style constants rather than as Swift enum +// values. +// This declares extern'ed constants (implemented in CDVPluginResult.m) +#define SWIFT_ENUM_COMPAT_HACK(enumVal) extern const CDVCommandStatus SWIFT_##enumVal NS_SWIFT_NAME(enumVal) +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_NO_RESULT); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_OK); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_CLASS_NOT_FOUND_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_ILLEGAL_ACCESS_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_INSTANTIATION_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_MALFORMED_URL_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_IO_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_INVALID_ACTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_JSON_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_ERROR); +#undef SWIFT_ENUM_COMPAT_HACK + +@interface CDVPluginResult : NSObject {} + +@property (nonatomic, strong, readonly) NSNumber* status; +@property (nonatomic, strong, readonly) id message; +@property (nonatomic, strong) NSNumber* keepCallback; +// This property can be used to scope the lifetime of another object. For example, +// Use it to store the associated NSData when `message` is created using initWithBytesNoCopy. +@property (nonatomic, strong) id associatedObject; + +- (CDVPluginResult*)init; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsString:(NSString*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArray:(NSArray*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsInt:(int)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsNSInteger:(NSInteger)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsNSUInteger:(NSUInteger)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDouble:(double)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsBool:(BOOL)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDictionary:(NSDictionary*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArrayBuffer:(NSData*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsMultipart:(NSArray*)theMessages; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode; + ++ (void)setVerbose:(BOOL)verbose; ++ (BOOL)isVerbose; + +- (void)setKeepCallbackAsBool:(BOOL)bKeepCallback; + +- (NSString*)argumentsAsJSON; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVPluginResult.m b/_platforms/ios/CordovaLib/Classes/Public/CDVPluginResult.m new file mode 100644 index 000000000..930aeaba7 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVPluginResult.m @@ -0,0 +1,203 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVPluginResult.h" +#import "CDVJSON_private.h" +#import "CDVDebug.h" + +// This exists to preserve compatibility with early Swift plugins, who are +// using CDVCommandStatus as ObjC-style constants rather than as Swift enum +// values. +// These constants alias the enum values back to their previous names. +#define SWIFT_ENUM_COMPAT_HACK(enumVal) const CDVCommandStatus SWIFT_##enumVal NS_SWIFT_NAME(enumVal) = enumVal +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_NO_RESULT); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_OK); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_CLASS_NOT_FOUND_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_ILLEGAL_ACCESS_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_INSTANTIATION_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_MALFORMED_URL_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_IO_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_INVALID_ACTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_JSON_EXCEPTION); +SWIFT_ENUM_COMPAT_HACK(CDVCommandStatus_ERROR); +#undef SWIFT_ENUM_COMPAT_HACK + +@interface CDVPluginResult () + +- (CDVPluginResult*)initWithStatus:(CDVCommandStatus)statusOrdinal message:(id)theMessage; + +@end + +@implementation CDVPluginResult +@synthesize status, message, keepCallback, associatedObject; + +static NSArray* org_apache_cordova_CommandStatusMsgs; + +id messageFromArrayBuffer(NSData* data) +{ + return @{ + @"CDVType" : @"ArrayBuffer", + @"data" :[data base64EncodedStringWithOptions:0] + }; +} + +id massageMessage(id message) +{ + if ([message isKindOfClass:[NSData class]]) { + return messageFromArrayBuffer(message); + } + return message; +} + +id messageFromMultipart(NSArray* theMessages) +{ + NSMutableArray* messages = [NSMutableArray arrayWithArray:theMessages]; + + for (NSUInteger i = 0; i < messages.count; ++i) { + [messages replaceObjectAtIndex:i withObject:massageMessage([messages objectAtIndex:i])]; + } + + return @{ + @"CDVType" : @"MultiPart", + @"messages" : messages + }; +} + ++ (void)initialize +{ + org_apache_cordova_CommandStatusMsgs = [[NSArray alloc] initWithObjects:@"No result", + @"OK", + @"Class not found", + @"Illegal access", + @"Instantiation error", + @"Malformed url", + @"IO error", + @"Invalid action", + @"JSON error", + @"Error", + nil]; +} + +- (CDVPluginResult*)init +{ + return [self initWithStatus:CDVCommandStatus_NO_RESULT message:nil]; +} + +- (CDVPluginResult*)initWithStatus:(CDVCommandStatus)statusOrdinal message:(id)theMessage +{ + self = [super init]; + if (self) { + status = @(statusOrdinal); + message = theMessage; + keepCallback = [NSNumber numberWithBool:NO]; + } + return self; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal +{ + return [[self alloc] initWithStatus:statusOrdinal message:nil]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsString:(NSString*)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:theMessage]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArray:(NSArray*)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:theMessage]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsInt:(int)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:[NSNumber numberWithInt:theMessage]]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsNSInteger:(NSInteger)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:[NSNumber numberWithInteger:theMessage]]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsNSUInteger:(NSUInteger)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:[NSNumber numberWithUnsignedInteger:theMessage]]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDouble:(double)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:[NSNumber numberWithDouble:theMessage]]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsBool:(BOOL)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:[NSNumber numberWithBool:theMessage]]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDictionary:(NSDictionary*)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:theMessage]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArrayBuffer:(NSData*)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:messageFromArrayBuffer(theMessage)]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsMultipart:(NSArray*)theMessages +{ + return [[self alloc] initWithStatus:statusOrdinal message:messageFromMultipart(theMessages)]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode +{ + NSDictionary* errDict = @{@"code" :[NSNumber numberWithInt:errorCode]}; + + return [[self alloc] initWithStatus:statusOrdinal message:errDict]; +} + +- (void)setKeepCallbackAsBool:(BOOL)bKeepCallback +{ + [self setKeepCallback:[NSNumber numberWithBool:bKeepCallback]]; +} + +- (NSString*)argumentsAsJSON +{ + id arguments = (self.message == nil ? [NSNull null] : self.message); + NSArray* argumentsWrappedInArray = [NSArray arrayWithObject:arguments]; + + NSString* argumentsJSON = [argumentsWrappedInArray cdv_JSONString]; + + argumentsJSON = [argumentsJSON substringWithRange:NSMakeRange(1, [argumentsJSON length] - 2)]; + + return argumentsJSON; +} + +static BOOL gIsVerbose = NO; ++ (void)setVerbose:(BOOL)verbose +{ + gIsVerbose = verbose; +} + ++ (BOOL)isVerbose +{ + return gIsVerbose; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVScreenOrientationDelegate.h b/_platforms/ios/CordovaLib/Classes/Public/CDVScreenOrientationDelegate.h new file mode 100644 index 000000000..49c3c2707 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVScreenOrientationDelegate.h @@ -0,0 +1,28 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@protocol CDVScreenOrientationDelegate + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations; + +- (BOOL)shouldAutorotate; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVTimer.h b/_platforms/ios/CordovaLib/Classes/Public/CDVTimer.h new file mode 100644 index 000000000..6d31593f2 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVTimer.h @@ -0,0 +1,27 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface CDVTimer : NSObject + ++ (void)start:(NSString*)name; ++ (void)stop:(NSString*)name; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVTimer.m b/_platforms/ios/CordovaLib/Classes/Public/CDVTimer.m new file mode 100644 index 000000000..784e94d38 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVTimer.m @@ -0,0 +1,123 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVTimer.h" + +#pragma mark CDVTimerItem + +@interface CDVTimerItem : NSObject + +@property (nonatomic, strong) NSString* name; +@property (nonatomic, strong) NSDate* started; +@property (nonatomic, strong) NSDate* ended; + +- (void)log; + +@end + +@implementation CDVTimerItem + +- (void)log +{ + NSLog(@"[CDVTimer][%@] %fms", self.name, [self.ended timeIntervalSinceDate:self.started] * 1000.0); +} + +@end + +#pragma mark CDVTimer + +@interface CDVTimer () + +@property (nonatomic, strong) NSMutableDictionary* items; + +@end + +@implementation CDVTimer + +#pragma mark object methods + +- (id)init +{ + if (self = [super init]) { + self.items = [NSMutableDictionary dictionaryWithCapacity:6]; + } + + return self; +} + +- (void)add:(NSString*)name +{ + if ([self.items objectForKey:[name lowercaseString]] == nil) { + CDVTimerItem* item = [CDVTimerItem new]; + item.name = name; + item.started = [NSDate new]; + [self.items setObject:item forKey:[name lowercaseString]]; + } else { + NSLog(@"Timer called '%@' already exists.", name); + } +} + +- (void)remove:(NSString*)name +{ + CDVTimerItem* item = [self.items objectForKey:[name lowercaseString]]; + + if (item != nil) { + item.ended = [NSDate new]; + [item log]; + [self.items removeObjectForKey:[name lowercaseString]]; + } else { + NSLog(@"Timer called '%@' does not exist.", name); + } +} + +- (void)removeAll +{ + [self.items removeAllObjects]; +} + +#pragma mark class methods + ++ (void)start:(NSString*)name +{ + [[CDVTimer sharedInstance] add:name]; +} + ++ (void)stop:(NSString*)name +{ + [[CDVTimer sharedInstance] remove:name]; +} + ++ (void)clearAll +{ + [[CDVTimer sharedInstance] removeAll]; +} + ++ (CDVTimer*)sharedInstance +{ + static dispatch_once_t pred = 0; + __strong static CDVTimer* _sharedObject = nil; + + dispatch_once(&pred, ^{ + _sharedObject = [[self alloc] init]; + }); + + return _sharedObject; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVURLSchemeHandler.h b/_platforms/ios/CordovaLib/Classes/Public/CDVURLSchemeHandler.h new file mode 100644 index 000000000..c074fa61c --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVURLSchemeHandler.h @@ -0,0 +1,34 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import +#import "CDVViewController.h" + + +@interface CDVURLSchemeHandler : NSObject + +@property (nonatomic, strong) CDVViewController* viewController; + +@property (nonatomic) CDVPlugin* schemePlugin; + +- (instancetype)initWithVC:(CDVViewController *)controller; + + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVURLSchemeHandler.m b/_platforms/ios/CordovaLib/Classes/Public/CDVURLSchemeHandler.m new file mode 100644 index 000000000..333124c2a --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVURLSchemeHandler.m @@ -0,0 +1,137 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + + +#import "CDVURLSchemeHandler.h" +#import + +#import + +@implementation CDVURLSchemeHandler + + +- (instancetype)initWithVC:(CDVViewController *)controller +{ + self = [super init]; + if (self) { + _viewController = controller; + } + return self; +} + +- (void)webView:(WKWebView *)webView startURLSchemeTask:(id )urlSchemeTask +{ + NSString * startPath = [[NSBundle mainBundle] pathForResource:self.viewController.wwwFolderName ofType: nil]; + NSURL * url = urlSchemeTask.request.URL; + NSString * stringToLoad = url.path; + NSString * scheme = url.scheme; + + CDVViewController* vc = (CDVViewController*)self.viewController; + + /* + * Give plugins the chance to handle the url + */ + BOOL anyPluginsResponded = NO; + BOOL handledRequest = NO; + + NSDictionary *pluginObjects = [[vc pluginObjects] copy]; + for (NSString* pluginName in pluginObjects) { + self.schemePlugin = [vc.pluginObjects objectForKey:pluginName]; + SEL selector = NSSelectorFromString(@"overrideSchemeTask:"); + if ([self.schemePlugin respondsToSelector:selector]) { + handledRequest = (((BOOL (*)(id, SEL, id ))objc_msgSend)(self.schemePlugin, selector, urlSchemeTask)); + if (handledRequest) { + anyPluginsResponded = YES; + break; + } + } + } + + if (!anyPluginsResponded) { + if ([scheme isEqualToString:self.viewController.appScheme]) { + if ([stringToLoad hasPrefix:@"/_app_file_"]) { + startPath = [stringToLoad stringByReplacingOccurrencesOfString:@"/_app_file_" withString:@""]; + } else { + if ([stringToLoad isEqualToString:@""] || [url.pathExtension isEqualToString:@""]) { + startPath = [startPath stringByAppendingPathComponent:self.viewController.startPage]; + } else { + startPath = [startPath stringByAppendingPathComponent:stringToLoad]; + } + } + } + + NSError * fileError = nil; + NSData * data = nil; + if ([self isMediaExtension:url.pathExtension]) { + data = [NSData dataWithContentsOfFile:startPath options:NSDataReadingMappedIfSafe error:&fileError]; + } + if (!data || fileError) { + data = [[NSData alloc] initWithContentsOfFile:startPath]; + } + NSInteger statusCode = 200; + if (!data) { + statusCode = 404; + } + NSURL * localUrl = [NSURL URLWithString:url.absoluteString]; + NSString * mimeType = [self getMimeType:url.pathExtension]; + id response = nil; + if (data && [self isMediaExtension:url.pathExtension]) { + response = [[NSURLResponse alloc] initWithURL:localUrl MIMEType:mimeType expectedContentLength:data.length textEncodingName:nil]; + } else { + NSDictionary * headers = @{ @"Content-Type" : mimeType, @"Cache-Control": @"no-cache"}; + response = [[NSHTTPURLResponse alloc] initWithURL:localUrl statusCode:statusCode HTTPVersion:nil headerFields:headers]; + } + + [urlSchemeTask didReceiveResponse:response]; + if (data) { + [urlSchemeTask didReceiveData:data]; + } + [urlSchemeTask didFinish]; + } +} + +- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask +{ + SEL selector = NSSelectorFromString(@"stopSchemeTask:"); + if (self.schemePlugin != nil && [self.schemePlugin respondsToSelector:selector]) { + (((void (*)(id, SEL, id ))objc_msgSend)(self.schemePlugin, selector, urlSchemeTask)); + } +} + +-(NSString *) getMimeType:(NSString *)fileExtension { + if (fileExtension && ![fileExtension isEqualToString:@""]) { + NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL); + NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); + return contentType ? contentType : @"application/octet-stream"; + } else { + return @"text/html"; + } +} + +-(BOOL) isMediaExtension:(NSString *) pathExtension { + NSArray * mediaExtensions = @[@"m4v", @"mov", @"mp4", + @"aac", @"ac3", @"aiff", @"au", @"flac", @"m4a", @"mp3", @"wav"]; + if ([mediaExtensions containsObject:pathExtension.lowercaseString]) { + return YES; + } + return NO; +} + + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVViewController.h b/_platforms/ios/CordovaLib/Classes/Public/CDVViewController.h new file mode 100644 index 000000000..81c7128f1 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVViewController.h @@ -0,0 +1,78 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import +#import "CDVAvailability.h" +#import "CDVInvokedUrlCommand.h" +#import "CDVCommandDelegate.h" +#import "CDVCommandQueue.h" +#import "CDVScreenOrientationDelegate.h" +#import "CDVPlugin.h" +#import "CDVWebViewEngineProtocol.h" + +@interface CDVViewController : UIViewController { + @protected + id _webViewEngine; + @protected + id _commandDelegate; + @protected + CDVCommandQueue* _commandQueue; +} + +@property (nonatomic, readonly, weak) IBOutlet UIView* webView; +@property (nonatomic, readonly, strong) UIView* launchView; + +@property (nonatomic, readonly, strong) NSMutableDictionary* pluginObjects; +@property (nonatomic, readonly, strong) NSDictionary* pluginsMap; +@property (nonatomic, readonly, strong) NSMutableDictionary* settings; +@property (nonatomic, readonly, strong) NSXMLParser* configParser; + +@property (nonatomic, readwrite, copy) NSString* appScheme; +@property (nonatomic, readwrite, copy) NSString* configFile; +@property (nonatomic, readwrite, copy) NSString* wwwFolderName; +@property (nonatomic, readwrite, copy) NSString* startPage; +@property (nonatomic, readonly, strong) CDVCommandQueue* commandQueue; +@property (nonatomic, readonly, strong) id webViewEngine; +@property (nonatomic, readonly, strong) id commandDelegate; + +/** + Takes/Gives an array of UIInterfaceOrientation (int) objects + ex. UIInterfaceOrientationPortrait +*/ +@property (nonatomic, readwrite, strong) NSArray* supportedOrientations; + +- (UIView*)newCordovaViewWithFrame:(CGRect)bounds; + +- (NSString*)appURLScheme; +- (NSURL*)errorURL; + +- (UIColor*)colorFromColorString:(NSString*)colorString CDV_DEPRECATED(7.0.0, "Use BackgroundColor in xcassets"); +- (NSArray*)parseInterfaceOrientations:(NSArray*)orientations; +- (BOOL)supportsOrientation:(UIInterfaceOrientation)orientation; + +- (id)getCommandInstance:(NSString*)pluginName; +- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className; +- (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName; + +- (void)parseSettingsWithParser:(NSObject *)delegate; + +- (void)showLaunchScreen:(BOOL)visible; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVViewController.m b/_platforms/ios/CordovaLib/Classes/Public/CDVViewController.m new file mode 100644 index 000000000..dcec9589e --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVViewController.m @@ -0,0 +1,819 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import "CDV.h" +#import "CDVPlugin+Private.h" +#import "CDVWebViewUIDelegate.h" +#import "CDVConfigParser.h" +#import +#import "NSDictionary+CordovaPreferences.h" +#import "CDVCommandDelegateImpl.h" +#import + +@interface CDVViewController () { } + +@property (nonatomic, readwrite, strong) NSXMLParser* configParser; +@property (nonatomic, readwrite, strong) NSMutableDictionary* settings; +@property (nonatomic, readwrite, strong) NSMutableDictionary* pluginObjects; +@property (nonatomic, readwrite, strong) NSMutableArray* startupPluginNames; +@property (nonatomic, readwrite, strong) NSDictionary* pluginsMap; +@property (nonatomic, readwrite, strong) id webViewEngine; +@property (nonatomic, readwrite, strong) UIView* launchView; + +@property (readwrite, assign) BOOL initialized; + +@property (atomic, strong) NSURL* openURL; + +@end + +@implementation CDVViewController + +@synthesize supportedOrientations; +@synthesize pluginObjects, pluginsMap, startupPluginNames; +@synthesize configParser, settings; +@synthesize wwwFolderName, startPage, initialized, openURL; +@synthesize commandDelegate = _commandDelegate; +@synthesize commandQueue = _commandQueue; +@synthesize webViewEngine = _webViewEngine; +@dynamic webView; + +- (void)__init +{ + if ((self != nil) && !self.initialized) { + _commandQueue = [[CDVCommandQueue alloc] initWithViewController:self]; + _commandDelegate = [[CDVCommandDelegateImpl alloc] initWithViewController:self]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillTerminate:) + name:UIApplicationWillTerminateNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillResignActive:) + name:UIApplicationWillResignActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidBecomeActive:) + name:UIApplicationDidBecomeActiveNotification object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onWebViewPageDidLoad:) + name:CDVPageDidLoadNotification object:nil]; + + // read from UISupportedInterfaceOrientations (or UISupportedInterfaceOrientations~iPad, if its iPad) from -Info.plist + self.supportedOrientations = [self parseInterfaceOrientations: + [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"]]; + + [self printVersion]; + [self printMultitaskingInfo]; + [self printPlatformVersionWarning]; + self.initialized = YES; + } +} + +- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + [self __init]; + return self; +} + +- (id)initWithCoder:(NSCoder*)aDecoder +{ + self = [super initWithCoder:aDecoder]; + [self __init]; + return self; +} + +- (id)init +{ + self = [super init]; + [self __init]; + return self; +} + +- (void)printVersion +{ + NSLog(@"Apache Cordova native platform version %@ is starting.", CDV_VERSION); +} + +- (void)printPlatformVersionWarning +{ + if (!IsAtLeastiOSVersion(@"8.0")) { + NSLog(@"CRITICAL: For Cordova 4.0.0 and above, you will need to upgrade to at least iOS 8.0 or greater. Your current version of iOS is %@.", + [[UIDevice currentDevice] systemVersion] + ); + } +} + +- (void)printMultitaskingInfo +{ + UIDevice* device = [UIDevice currentDevice]; + BOOL backgroundSupported = NO; + + if ([device respondsToSelector:@selector(isMultitaskingSupported)]) { + backgroundSupported = device.multitaskingSupported; + } + + NSNumber* exitsOnSuspend = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIApplicationExitsOnSuspend"]; + if (exitsOnSuspend == nil) { // if it's missing, it should be NO (i.e. multi-tasking on by default) + exitsOnSuspend = [NSNumber numberWithBool:NO]; + } + + NSLog(@"Multi-tasking -> Device: %@, App: %@", (backgroundSupported ? @"YES" : @"NO"), (![exitsOnSuspend intValue]) ? @"YES" : @"NO"); +} + +-(NSString*)configFilePath{ + NSString* path = self.configFile ?: @"config.xml"; + + // if path is relative, resolve it against the main bundle + if(![path isAbsolutePath]){ + NSString* absolutePath = [[NSBundle mainBundle] pathForResource:path ofType:nil]; + if(!absolutePath){ + NSAssert(NO, @"ERROR: %@ not found in the main bundle!", path); + } + path = absolutePath; + } + + // Assert file exists + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + NSAssert(NO, @"ERROR: %@ does not exist. Please run cordova-ios/bin/cordova_plist_to_config_xml path/to/project.", path); + return nil; + } + + return path; +} + +- (void)parseSettingsWithParser:(NSObject *)delegate +{ + // read from config.xml in the app bundle + NSString* path = [self configFilePath]; + + NSURL* url = [NSURL fileURLWithPath:path]; + + self.configParser = [[NSXMLParser alloc] initWithContentsOfURL:url]; + if (self.configParser == nil) { + NSLog(@"Failed to initialize XML parser."); + return; + } + [self.configParser setDelegate:((id < NSXMLParserDelegate >)delegate)]; + [self.configParser parse]; +} + +- (void)loadSettings +{ + CDVConfigParser* delegate = [[CDVConfigParser alloc] init]; + + [self parseSettingsWithParser:delegate]; + + // Get the plugin dictionary, whitelist and settings from the delegate. + self.pluginsMap = delegate.pluginsDict; + self.startupPluginNames = delegate.startupPluginNames; + self.settings = delegate.settings; + + // And the start folder/page. + if(self.wwwFolderName == nil){ + self.wwwFolderName = @"www"; + } + if(delegate.startPage && self.startPage == nil){ + self.startPage = delegate.startPage; + } + if (self.startPage == nil) { + self.startPage = @"index.html"; + } + + // Initialize the plugin objects dict. + self.pluginObjects = [[NSMutableDictionary alloc] initWithCapacity:20]; +} + +- (NSURL*)appUrl +{ + NSURL* appURL = nil; + + if ([self.startPage rangeOfString:@"://"].location != NSNotFound) { + appURL = [NSURL URLWithString:self.startPage]; + } else if ([self.wwwFolderName rangeOfString:@"://"].location != NSNotFound) { + appURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", self.wwwFolderName, self.startPage]]; + } else if([self.wwwFolderName rangeOfString:@".bundle"].location != NSNotFound){ + // www folder is actually a bundle + NSBundle* bundle = [NSBundle bundleWithPath:self.wwwFolderName]; + appURL = [bundle URLForResource:self.startPage withExtension:nil]; + } else if([self.wwwFolderName rangeOfString:@".framework"].location != NSNotFound){ + // www folder is actually a framework + NSBundle* bundle = [NSBundle bundleWithPath:self.wwwFolderName]; + appURL = [bundle URLForResource:self.startPage withExtension:nil]; + } else { + // CB-3005 strip parameters from start page to check if page exists in resources + NSURL* startURL = [NSURL URLWithString:self.startPage]; + NSString* startFilePath = [self.commandDelegate pathForResource:[startURL path]]; + + if (startFilePath == nil) { + appURL = nil; + } else { + appURL = [NSURL fileURLWithPath:startFilePath]; + // CB-3005 Add on the query params or fragment. + NSString* startPageNoParentDirs = self.startPage; + NSRange r = [startPageNoParentDirs rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"?#"] options:0]; + if (r.location != NSNotFound) { + NSString* queryAndOrFragment = [self.startPage substringFromIndex:r.location]; + appURL = [NSURL URLWithString:queryAndOrFragment relativeToURL:appURL]; + } + } + } + + return appURL; +} + +- (NSURL*)errorURL +{ + NSURL* errorUrl = nil; + + id setting = [self.settings cordovaSettingForKey:@"ErrorUrl"]; + + if (setting) { + NSString* errorUrlString = (NSString*)setting; + if ([errorUrlString rangeOfString:@"://"].location != NSNotFound) { + errorUrl = [NSURL URLWithString:errorUrlString]; + } else { + NSURL* url = [NSURL URLWithString:(NSString*)setting]; + NSString* errorFilePath = [self.commandDelegate pathForResource:[url path]]; + if (errorFilePath) { + errorUrl = [NSURL fileURLWithPath:errorFilePath]; + } + } + } + + return errorUrl; +} + +- (UIView*)webView +{ + if (self.webViewEngine != nil) { + return self.webViewEngine.engineWebView; + } + + return nil; +} + +// Implement viewDidLoad to do additional setup after loading the view, typically from a nib. +- (void)viewDidLoad +{ + [super viewDidLoad]; + + // Load settings + [self loadSettings]; + + NSString* backupWebStorageType = @"cloud"; // default value + + id backupWebStorage = [self.settings cordovaSettingForKey:@"BackupWebStorage"]; + if ([backupWebStorage isKindOfClass:[NSString class]]) { + backupWebStorageType = backupWebStorage; + } + [self.settings setCordovaSetting:backupWebStorageType forKey:@"BackupWebStorage"]; + + // // Instantiate the Launch screen ///////// + + if (!self.launchView) { + [self createLaunchView]; + } + + // // Instantiate the WebView /////////////// + + if (!self.webView) { + [self createGapView]; + } + + // ///////////////// + + if ([self.startupPluginNames count] > 0) { + [CDVTimer start:@"TotalPluginStartup"]; + + for (NSString* pluginName in self.startupPluginNames) { + [CDVTimer start:pluginName]; + [self getCommandInstance:pluginName]; + [CDVTimer stop:pluginName]; + } + + [CDVTimer stop:@"TotalPluginStartup"]; + } + + // ///////////////// + NSURL* appURL = [self appUrl]; + + if (appURL) { + NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0]; + [self.webViewEngine loadRequest:appReq]; + } else { + NSString* loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", self.wwwFolderName, self.startPage]; + NSLog(@"%@", loadErr); + + NSURL* errorUrl = [self errorURL]; + if (errorUrl) { + errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [loadErr stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet]] relativeToURL:errorUrl]; + NSLog(@"%@", [errorUrl absoluteString]); + [self.webViewEngine loadRequest:[NSURLRequest requestWithURL:errorUrl]]; + } else { + NSString* html = [NSString stringWithFormat:@" %@ ", loadErr]; + [self.webViewEngine loadHTMLString:html baseURL:nil]; + } + } + // ///////////////// + + UIColor* bgColor = [UIColor colorNamed:@"BackgroundColor"] ?: UIColor.whiteColor; + [self.launchView setBackgroundColor:bgColor]; + [self.webView setBackgroundColor:bgColor]; +} + +-(void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVViewWillAppearNotification object:nil]]; +} + +-(void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVViewDidAppearNotification object:nil]]; +} + +-(void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVViewWillDisappearNotification object:nil]]; +} + +-(void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVViewDidDisappearNotification object:nil]]; +} + +-(void)viewWillLayoutSubviews +{ + [super viewWillLayoutSubviews]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVViewWillLayoutSubviewsNotification object:nil]]; +} + +-(void)viewDidLayoutSubviews +{ + [super viewDidLayoutSubviews]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVViewDidLayoutSubviewsNotification object:nil]]; +} + +-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator +{ + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVViewWillTransitionToSizeNotification object:[NSValue valueWithCGSize:size]]]; +} + +- (UIColor*)colorFromColorString:(NSString*)colorString +{ + // No value, nothing to do + if (!colorString) { + return nil; + } + + // Validate format + NSError* error = NULL; + NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:@"^(#[0-9A-F]{3}|(0x|#)([0-9A-F]{2})?[0-9A-F]{6})$" options:NSRegularExpressionCaseInsensitive error:&error]; + NSUInteger countMatches = [regex numberOfMatchesInString:colorString options:0 range:NSMakeRange(0, [colorString length])]; + + if (!countMatches) { + return nil; + } + + // #FAB to #FFAABB + if ([colorString hasPrefix:@"#"] && [colorString length] == 4) { + NSString* r = [colorString substringWithRange:NSMakeRange(1, 1)]; + NSString* g = [colorString substringWithRange:NSMakeRange(2, 1)]; + NSString* b = [colorString substringWithRange:NSMakeRange(3, 1)]; + colorString = [NSString stringWithFormat:@"#%@%@%@%@%@%@", r, r, g, g, b, b]; + } + + // #RRGGBB to 0xRRGGBB + colorString = [colorString stringByReplacingOccurrencesOfString:@"#" withString:@"0x"]; + + // 0xRRGGBB to 0xAARRGGBB + if ([colorString hasPrefix:@"0x"] && [colorString length] == 8) { + colorString = [@"0xFF" stringByAppendingString:[colorString substringFromIndex:2]]; + } + + // 0xAARRGGBB to int + unsigned colorValue = 0; + NSScanner *scanner = [NSScanner scannerWithString:colorString]; + if (![scanner scanHexInt:&colorValue]) { + return nil; + } + + // int to UIColor + return [UIColor colorWithRed:((float)((colorValue & 0x00FF0000) >> 16))/255.0 + green:((float)((colorValue & 0x0000FF00) >> 8))/255.0 + blue:((float)((colorValue & 0x000000FF) >> 0))/255.0 + alpha:((float)((colorValue & 0xFF000000) >> 24))/255.0]; +} + +- (NSArray*)parseInterfaceOrientations:(NSArray*)orientations +{ + NSMutableArray* result = [[NSMutableArray alloc] init]; + + if (orientations != nil) { + NSEnumerator* enumerator = [orientations objectEnumerator]; + NSString* orientationString; + + while (orientationString = [enumerator nextObject]) { + if ([orientationString isEqualToString:@"UIInterfaceOrientationPortrait"]) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]]; + } else if ([orientationString isEqualToString:@"UIInterfaceOrientationPortraitUpsideDown"]) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortraitUpsideDown]]; + } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeLeft"]) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft]]; + } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeRight"]) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight]]; + } + } + } + + // default + if ([result count] == 0) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]]; + } + + return result; +} + +- (BOOL)shouldAutorotate +{ + return YES; +} + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations +{ + NSUInteger ret = 0; + + if ([self supportsOrientation:UIInterfaceOrientationPortrait]) { + ret = ret | (1 << UIInterfaceOrientationPortrait); + } + if ([self supportsOrientation:UIInterfaceOrientationPortraitUpsideDown]) { + ret = ret | (1 << UIInterfaceOrientationPortraitUpsideDown); + } + if ([self supportsOrientation:UIInterfaceOrientationLandscapeRight]) { + ret = ret | (1 << UIInterfaceOrientationLandscapeRight); + } + if ([self supportsOrientation:UIInterfaceOrientationLandscapeLeft]) { + ret = ret | (1 << UIInterfaceOrientationLandscapeLeft); + } + + return ret; +} + +- (BOOL)supportsOrientation:(UIInterfaceOrientation)orientation +{ + return [self.supportedOrientations containsObject:@(orientation)]; +} + +- (UIView*)newCordovaViewWithFrame:(CGRect)bounds +{ + NSString* defaultWebViewEngineClass = [self.settings cordovaSettingForKey:@"CordovaDefaultWebViewEngine"]; + NSString* webViewEngineClass = [self.settings cordovaSettingForKey:@"CordovaWebViewEngine"]; + + if (!defaultWebViewEngineClass) { + defaultWebViewEngineClass = @"CDVWebViewEngine"; + } + if (!webViewEngineClass) { + webViewEngineClass = defaultWebViewEngineClass; + } + + // Find webViewEngine + if (NSClassFromString(webViewEngineClass)) { + self.webViewEngine = [[NSClassFromString(webViewEngineClass) alloc] initWithFrame:bounds]; + // if a webView engine returns nil (not supported by the current iOS version) or doesn't conform to the protocol, or can't load the request, we use WKWebView + if (!self.webViewEngine || ![self.webViewEngine conformsToProtocol:@protocol(CDVWebViewEngineProtocol)] || ![self.webViewEngine canLoadRequest:[NSURLRequest requestWithURL:self.appUrl]]) { + self.webViewEngine = [[NSClassFromString(defaultWebViewEngineClass) alloc] initWithFrame:bounds]; + } + } else { + self.webViewEngine = [[NSClassFromString(defaultWebViewEngineClass) alloc] initWithFrame:bounds]; + } + + if ([self.webViewEngine isKindOfClass:[CDVPlugin class]]) { + [self registerPlugin:(CDVPlugin*)self.webViewEngine withClassName:webViewEngineClass]; + } + + return self.webViewEngine.engineWebView; +} + +- (void)createLaunchView +{ + CGRect webViewBounds = self.view.bounds; + webViewBounds.origin = self.view.bounds.origin; + + UIView* view = [[UIView alloc] initWithFrame:webViewBounds]; + [view setAlpha:0]; + + NSString* launchStoryboardName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchStoryboardName"]; + if (launchStoryboardName != nil) { + UIStoryboard* storyboard = [UIStoryboard storyboardWithName:launchStoryboardName bundle:[NSBundle mainBundle]]; + UIViewController* vc = [storyboard instantiateInitialViewController]; + + [view addSubview:vc.view]; + } + + self.launchView = view; + [self.view addSubview:view]; +} + +- (void)createGapView +{ + CGRect webViewBounds = self.view.bounds; + webViewBounds.origin = self.view.bounds.origin; + + UIView* view = [self newCordovaViewWithFrame:webViewBounds]; + view.hidden = YES; + view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + + [self.view addSubview:view]; + [self.view sendSubviewToBack:view]; +} + +- (void)didReceiveMemoryWarning +{ + // iterate through all the plugin objects, and call hasPendingOperation + // if at least one has a pending operation, we don't call [super didReceiveMemoryWarning] + + NSEnumerator* enumerator = [self.pluginObjects objectEnumerator]; + CDVPlugin* plugin; + + BOOL doPurge = YES; + + while ((plugin = [enumerator nextObject])) { + if (plugin.hasPendingOperation) { + NSLog(@"Plugin '%@' has a pending operation, memory purge is delayed for didReceiveMemoryWarning.", NSStringFromClass([plugin class])); + doPurge = NO; + } + } + + if (doPurge) { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + } + + // Release any cached data, images, etc. that aren't in use. +} + +#pragma mark CordovaCommands + +- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className +{ + if ([plugin respondsToSelector:@selector(setViewController:)]) { + [plugin setViewController:self]; + } + + if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) { + [plugin setCommandDelegate:_commandDelegate]; + } + + [self.pluginObjects setObject:plugin forKey:className]; + [plugin pluginInitialize]; +} + +- (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName +{ + if ([plugin respondsToSelector:@selector(setViewController:)]) { + [plugin setViewController:self]; + } + + if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) { + [plugin setCommandDelegate:_commandDelegate]; + } + + NSString* className = NSStringFromClass([plugin class]); + [self.pluginObjects setObject:plugin forKey:className]; + [self.pluginsMap setValue:className forKey:[pluginName lowercaseString]]; + [plugin pluginInitialize]; +} + +/** + Returns an instance of a CordovaCommand object, based on its name. If one exists already, it is returned. + */ +- (id)getCommandInstance:(NSString*)pluginName +{ + // first, we try to find the pluginName in the pluginsMap + // (acts as a whitelist as well) if it does not exist, we return nil + // NOTE: plugin names are matched as lowercase to avoid problems - however, a + // possible issue is there can be duplicates possible if you had: + // "org.apache.cordova.Foo" and "org.apache.cordova.foo" - only the lower-cased entry will match + NSString* className = [self.pluginsMap objectForKey:[pluginName lowercaseString]]; + + if (className == nil) { + return nil; + } + + id obj = [self.pluginObjects objectForKey:className]; + if (!obj) { + obj = [[NSClassFromString(className)alloc] initWithWebViewEngine:_webViewEngine]; + if (!obj) { + NSString* fullClassName = [NSString stringWithFormat:@"%@.%@", + NSBundle.mainBundle.infoDictionary[@"CFBundleExecutable"], + className]; + obj = [[NSClassFromString(fullClassName)alloc] initWithWebViewEngine:_webViewEngine]; + } + + if (obj != nil) { + [self registerPlugin:obj withClassName:className]; + } else { + NSLog(@"CDVPlugin class %@ (pluginName: %@) does not exist.", className, pluginName); + } + } + return obj; +} + +#pragma mark - + +- (NSString*)appURLScheme +{ + NSString* URLScheme = nil; + + NSArray* URLTypes = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleURLTypes"]; + + if (URLTypes != nil) { + NSDictionary* dict = [URLTypes objectAtIndex:0]; + if (dict != nil) { + NSArray* URLSchemes = [dict objectForKey:@"CFBundleURLSchemes"]; + if (URLSchemes != nil) { + URLScheme = [URLSchemes objectAtIndex:0]; + } + } + } + + return URLScheme; +} + +#pragma mark - +#pragma mark UIApplicationDelegate impl + +/* + This method lets your application know that it is about to be terminated and purged from memory entirely + */ +- (void)onAppWillTerminate:(NSNotification*)notification +{ + // empty the tmp directory + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSError* __autoreleasing err = nil; + + // clear contents of NSTemporaryDirectory + NSString* tempDirectoryPath = NSTemporaryDirectory(); + NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath]; + NSString* fileName = nil; + BOOL result; + + while ((fileName = [directoryEnumerator nextObject])) { + NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName]; + result = [fileMgr removeItemAtPath:filePath error:&err]; + if (!result && err) { + NSLog(@"Failed to delete: %@ (error: %@)", filePath, err); + } + } +} + +- (bool)isUrlEmpty:(NSURL *)url +{ + if (!url || (url == (id) [NSNull null])) { + return true; + } + NSString *urlAsString = [url absoluteString]; + return (urlAsString == (id) [NSNull null] || [urlAsString length]==0 || [urlAsString isEqualToString:@"about:blank"]); +} + +- (bool)checkAndReinitViewUrl +{ + NSURL* appURL = [self appUrl]; + if ([self isUrlEmpty: [self.webViewEngine URL]] && ![self isUrlEmpty: appURL]) { + NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0]; + [self.webViewEngine loadRequest:appReq]; + return true; + } + return false; +} + +/* + This method is called to let your application know that it is about to move from the active to inactive state. + You should use this method to pause ongoing tasks, disable timer, ... + */ +- (void)onAppWillResignActive:(NSNotification*)notification +{ + [self checkAndReinitViewUrl]; + // NSLog(@"%@",@"applicationWillResignActive"); + [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('resign');" scheduledOnRunLoop:NO]; +} + +/* + In iOS 4.0 and later, this method is called as part of the transition from the background to the inactive state. + You can use this method to undo many of the changes you made to your application upon entering the background. + invariably followed by applicationDidBecomeActive + */ +- (void)onAppWillEnterForeground:(NSNotification*)notification +{ + [self checkAndReinitViewUrl]; + // NSLog(@"%@",@"applicationWillEnterForeground"); + [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('resume');"]; + + if (!IsAtLeastiOSVersion(@"11.0")) { + /** Clipboard fix **/ + UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; + NSString* string = pasteboard.string; + if (string) { + [pasteboard setValue:string forPasteboardType:@"public.text"]; + } + } +} + +// This method is called to let your application know that it moved from the inactive to active state. +- (void)onAppDidBecomeActive:(NSNotification*)notification +{ + [self checkAndReinitViewUrl]; + // NSLog(@"%@",@"applicationDidBecomeActive"); + [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('active');"]; +} + +/* + In iOS 4.0 and later, this method is called instead of the applicationWillTerminate: method + when the user quits an application that supports background execution. + */ +- (void)onAppDidEnterBackground:(NSNotification*)notification +{ + [self checkAndReinitViewUrl]; + // NSLog(@"%@",@"applicationDidEnterBackground"); + [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('pause', null, true);" scheduledOnRunLoop:NO]; +} + +/** + Show the webview and fade out the intermediary view + This is to prevent the flashing of the mainViewController + */ +- (void)onWebViewPageDidLoad:(NSNotification*)notification +{ + self.webView.hidden = NO; + + if ([self.settings cordovaBoolSettingForKey:@"AutoHideSplashScreen" defaultValue:YES]) { + CGFloat splashScreenDelaySetting = [self.settings cordovaFloatSettingForKey:@"SplashScreenDelay" defaultValue:0]; + + if (splashScreenDelaySetting == 0) { + [self showLaunchScreen:NO]; + } else { + // Divide by 1000 because config returns milliseconds and NSTimer takes seconds + CGFloat splashScreenDelay = splashScreenDelaySetting / 1000; + + [NSTimer scheduledTimerWithTimeInterval:splashScreenDelay repeats:NO block:^(NSTimer * _Nonnull timer) { + [self showLaunchScreen:NO]; + }]; + } + } +} + +/** + Method to be called from the plugin JavaScript to show or hide the launch screen. + */ +- (void)showLaunchScreen:(BOOL)visible +{ + CGFloat fadeSplashScreenDuration = [self.settings cordovaFloatSettingForKey:@"FadeSplashScreenDuration" defaultValue:250]; + + // Setting minimum value for fade to 0.25 seconds + fadeSplashScreenDuration = fadeSplashScreenDuration < 250 ? 250 : fadeSplashScreenDuration; + + // AnimateWithDuration takes seconds but cordova documentation specifies milliseconds + CGFloat fadeDuration = fadeSplashScreenDuration/1000; + + [UIView animateWithDuration:fadeDuration animations:^{ + [self.launchView setAlpha:(visible ? 1 : 0)]; + }]; +} + +// /////////////////////// + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [_commandQueue dispose]; + [[self.pluginObjects allValues] makeObjectsPerformSelector:@selector(dispose)]; + + [self.webViewEngine loadHTMLString:@"about:blank" baseURL:nil]; + [self.pluginObjects removeAllObjects]; + [self.webView removeFromSuperview]; + self.webViewEngine = nil; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h b/_platforms/ios/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h new file mode 100644 index 000000000..e6126a4b4 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h @@ -0,0 +1,41 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +#define kCDVWebViewEngineScriptMessageHandlers @"kCDVWebViewEngineScriptMessageHandlers" +#define kCDVWebViewEngineWKNavigationDelegate @"kCDVWebViewEngineWKNavigationDelegate" +#define kCDVWebViewEngineWKUIDelegate @"kCDVWebViewEngineWKUIDelegate" +#define kCDVWebViewEngineWebViewPreferences @"kCDVWebViewEngineWebViewPreferences" + +@protocol CDVWebViewEngineProtocol + +@property (nonatomic, strong, readonly) UIView* engineWebView; + +- (id)loadRequest:(NSURLRequest*)request; +- (id)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL; +- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler; + +- (NSURL*)URL; +- (BOOL)canLoadRequest:(NSURLRequest*)request; + +- (instancetype)initWithFrame:(CGRect)frame; +- (void)updateWithInfo:(NSDictionary*)info; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVWebViewProcessPoolFactory.h b/_platforms/ios/CordovaLib/Classes/Public/CDVWebViewProcessPoolFactory.h new file mode 100644 index 000000000..b15628914 --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVWebViewProcessPoolFactory.h @@ -0,0 +1,27 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface CDVWebViewProcessPoolFactory : NSObject +@property (nonatomic, retain) WKProcessPool* sharedPool; + ++(instancetype) sharedFactory; +-(WKProcessPool*) sharedProcessPool; +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVWebViewProcessPoolFactory.m b/_platforms/ios/CordovaLib/Classes/Public/CDVWebViewProcessPoolFactory.m new file mode 100644 index 000000000..577624e0f --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVWebViewProcessPoolFactory.m @@ -0,0 +1,49 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import +#import "CDVWebViewProcessPoolFactory.h" + +static CDVWebViewProcessPoolFactory *factory = nil; + +@implementation CDVWebViewProcessPoolFactory + ++ (instancetype)sharedFactory +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + factory = [[CDVWebViewProcessPoolFactory alloc] init]; + }); + + return factory; +} + +- (instancetype)init +{ + if (self = [super init]) { + _sharedPool = [[WKProcessPool alloc] init]; + } + return self; +} + +- (WKProcessPool*) sharedProcessPool { + return _sharedPool; +} +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVWhitelist.h b/_platforms/ios/CordovaLib/Classes/Public/CDVWhitelist.h new file mode 100644 index 000000000..91650970b --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVWhitelist.h @@ -0,0 +1,34 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +extern NSString* const kCDVDefaultWhitelistRejectionString; + +@interface CDVWhitelist : NSObject + +@property (nonatomic, copy) NSString* whitelistRejectionFormatString; + +- (id)initWithArray:(NSArray*)array; +- (BOOL)schemeIsAllowed:(NSString*)scheme; +- (BOOL)URLIsAllowed:(NSURL*)url; +- (BOOL)URLIsAllowed:(NSURL*)url logFailure:(BOOL)logFailure; +- (NSString*)errorStringForURL:(NSURL*)url; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/CDVWhitelist.m b/_platforms/ios/CordovaLib/Classes/Public/CDVWhitelist.m new file mode 100644 index 000000000..758f4d1ef --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/CDVWhitelist.m @@ -0,0 +1,285 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVWhitelist.h" + +NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejection: url='%@'"; +NSString* const kCDVDefaultSchemeName = @"cdv-default-scheme"; + +@interface CDVWhitelistPattern : NSObject { + @private + NSRegularExpression* _scheme; + NSRegularExpression* _host; + NSNumber* _port; + NSRegularExpression* _path; +} + ++ (NSString*)regexFromPattern:(NSString*)pattern allowWildcards:(bool)allowWildcards; +- (id)initWithScheme:(NSString*)scheme host:(NSString*)host port:(NSString*)port path:(NSString*)path; +- (bool)matches:(NSURL*)url; + +@end + +@implementation CDVWhitelistPattern + ++ (NSString*)regexFromPattern:(NSString*)pattern allowWildcards:(bool)allowWildcards +{ + NSString* regex = [NSRegularExpression escapedPatternForString:pattern]; + + if (allowWildcards) { + regex = [regex stringByReplacingOccurrencesOfString:@"\\*" withString:@".*"]; + + /* [NSURL path] has the peculiarity that a trailing slash at the end of a path + * will be omitted. This regex tweak compensates for that. + */ + if ([regex hasSuffix:@"\\/.*"]) { + regex = [NSString stringWithFormat:@"%@(\\/.*)?", [regex substringToIndex:([regex length] - 4)]]; + } + } + return [NSString stringWithFormat:@"%@$", regex]; +} + +- (id)initWithScheme:(NSString*)scheme host:(NSString*)host port:(NSString*)port path:(NSString*)path +{ + self = [super init]; // Potentially change "self" + if (self) { + if ((scheme == nil) || [scheme isEqualToString:@"*"]) { + _scheme = nil; + } else { + _scheme = [NSRegularExpression regularExpressionWithPattern:[CDVWhitelistPattern regexFromPattern:scheme allowWildcards:NO] options:NSRegularExpressionCaseInsensitive error:nil]; + } + if ([host isEqualToString:@"*"] || host == nil) { + _host = nil; + } else if ([host hasPrefix:@"*."]) { + _host = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"([a-z0-9.-]*\\.)?%@", [CDVWhitelistPattern regexFromPattern:[host substringFromIndex:2] allowWildcards:false]] options:NSRegularExpressionCaseInsensitive error:nil]; + } else { + _host = [NSRegularExpression regularExpressionWithPattern:[CDVWhitelistPattern regexFromPattern:host allowWildcards:NO] options:NSRegularExpressionCaseInsensitive error:nil]; + } + if ((port == nil) || [port isEqualToString:@"*"]) { + _port = nil; + } else { + _port = [[NSNumber alloc] initWithInteger:[port integerValue]]; + } + if ((path == nil) || [path isEqualToString:@"/*"]) { + _path = nil; + } else { + _path = [NSRegularExpression regularExpressionWithPattern:[CDVWhitelistPattern regexFromPattern:path allowWildcards:YES] options:0 error:nil]; + } + } + return self; +} + +- (bool)matches:(NSURL*)url +{ + return (_scheme == nil || [_scheme numberOfMatchesInString:[url scheme] options:NSMatchingAnchored range:NSMakeRange(0, [[url scheme] length])]) && + (_host == nil || ([url host] != nil && [_host numberOfMatchesInString:[url host] options:NSMatchingAnchored range:NSMakeRange(0, [[url host] length])])) && + (_port == nil || [[url port] isEqualToNumber:_port]) && + (_path == nil || [_path numberOfMatchesInString:[url path] options:NSMatchingAnchored range:NSMakeRange(0, [[url path] length])]) + ; +} + +@end + +@interface CDVWhitelist () + +@property (nonatomic, readwrite, strong) NSMutableArray* whitelist; +@property (nonatomic, readwrite, strong) NSMutableSet* permittedSchemes; + +- (void)addWhiteListEntry:(NSString*)pattern; + +@end + +@implementation CDVWhitelist + +@synthesize whitelist, permittedSchemes, whitelistRejectionFormatString; + +- (id)initWithArray:(NSArray*)array +{ + self = [super init]; + if (self) { + self.whitelist = [[NSMutableArray alloc] init]; + self.permittedSchemes = [[NSMutableSet alloc] init]; + self.whitelistRejectionFormatString = kCDVDefaultWhitelistRejectionString; + + for (NSString* pattern in array) { + [self addWhiteListEntry:pattern]; + } + } + return self; +} + +- (BOOL)isIPv4Address:(NSString*)externalHost +{ + // an IPv4 address has 4 octets b.b.b.b where b is a number between 0 and 255. + // for our purposes, b can also be the wildcard character '*' + + // we could use a regex to solve this problem but then I would have two problems + // anyways, this is much clearer and maintainable + NSArray* octets = [externalHost componentsSeparatedByString:@"."]; + NSUInteger num_octets = [octets count]; + + // quick check + if (num_octets != 4) { + return NO; + } + + // restrict number parsing to 0-255 + NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; + [numberFormatter setMinimum:[NSNumber numberWithUnsignedInteger:0]]; + [numberFormatter setMaximum:[NSNumber numberWithUnsignedInteger:255]]; + + // iterate through each octet, and test for a number between 0-255 or if it equals '*' + for (NSUInteger i = 0; i < num_octets; ++i) { + NSString* octet = [octets objectAtIndex:i]; + + if ([octet isEqualToString:@"*"]) { // passes - check next octet + continue; + } else if ([numberFormatter numberFromString:octet] == nil) { // fails - not a number and not within our range, return + return NO; + } + } + + return YES; +} + +- (void)addWhiteListEntry:(NSString*)origin +{ + if (self.whitelist == nil) { + return; + } + + if ([origin isEqualToString:@"*"]) { + NSLog(@"Unlimited access to network resources"); + self.whitelist = nil; + self.permittedSchemes = nil; + } else { // specific access + NSRegularExpression* parts = [NSRegularExpression regularExpressionWithPattern:@"^((\\*|[A-Za-z-]+):/?/?)?(((\\*\\.)?[^*/:]+)|\\*)?(:(\\d+))?(/.*)?" options:0 error:nil]; + NSTextCheckingResult* m = [parts firstMatchInString:origin options:NSMatchingAnchored range:NSMakeRange(0, [origin length])]; + if (m != nil) { + NSRange r; + NSString* scheme = nil; + r = [m rangeAtIndex:2]; + if (r.location != NSNotFound) { + scheme = [origin substringWithRange:r]; + } + + NSString* host = nil; + r = [m rangeAtIndex:3]; + if (r.location != NSNotFound) { + host = [origin substringWithRange:r]; + } + + // Special case for two urls which are allowed to have empty hosts + if (([scheme isEqualToString:@"file"] || [scheme isEqualToString:@"content"]) && (host == nil)) { + host = @"*"; + } + + NSString* port = nil; + r = [m rangeAtIndex:7]; + if (r.location != NSNotFound) { + port = [origin substringWithRange:r]; + } + + NSString* path = nil; + r = [m rangeAtIndex:8]; + if (r.location != NSNotFound) { + path = [origin substringWithRange:r]; + } + + if (scheme == nil) { + // XXX making it stupid friendly for people who forget to include protocol/SSL + [self.whitelist addObject:[[CDVWhitelistPattern alloc] initWithScheme:@"http" host:host port:port path:path]]; + [self.whitelist addObject:[[CDVWhitelistPattern alloc] initWithScheme:@"https" host:host port:port path:path]]; + } else { + [self.whitelist addObject:[[CDVWhitelistPattern alloc] initWithScheme:scheme host:host port:port path:path]]; + } + + if (self.permittedSchemes != nil) { + if ([scheme isEqualToString:@"*"]) { + self.permittedSchemes = nil; + } else if (scheme != nil) { + [self.permittedSchemes addObject:scheme]; + } + } + } + } +} + +- (BOOL)schemeIsAllowed:(NSString*)scheme +{ + if ([scheme isEqualToString:@"http"] || + [scheme isEqualToString:@"https"] || + [scheme isEqualToString:@"ftp"] || + [scheme isEqualToString:@"ftps"]) { + return YES; + } + + return (self.permittedSchemes == nil) || [self.permittedSchemes containsObject:scheme]; +} + +- (BOOL)URLIsAllowed:(NSURL*)url +{ + return [self URLIsAllowed:url logFailure:YES]; +} + +- (BOOL)URLIsAllowed:(NSURL*)url logFailure:(BOOL)logFailure +{ + // Shortcut acceptance: Are all urls whitelisted ("*" in whitelist)? + if (whitelist == nil) { + return YES; + } + + // Shortcut rejection: Check that the scheme is supported + NSString* scheme = [[url scheme] lowercaseString]; + if (![self schemeIsAllowed:scheme]) { + if (logFailure) { + NSLog(@"%@", [self errorStringForURL:url]); + } + return NO; + } + + // http[s] and ftp[s] should also validate against the common set in the kCDVDefaultSchemeName list + if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"ftp"] || [scheme isEqualToString:@"ftps"]) { + NSURL* newUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@%@", kCDVDefaultSchemeName, [url host], [[url path] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet]]]; + // If it is allowed, we are done. If not, continue to check for the actual scheme-specific list + if ([self URLIsAllowed:newUrl logFailure:NO]) { + return YES; + } + } + + // Check the url against patterns in the whitelist + for (CDVWhitelistPattern* p in self.whitelist) { + if ([p matches:url]) { + return YES; + } + } + + if (logFailure) { + NSLog(@"%@", [self errorStringForURL:url]); + } + // if we got here, the url host is not in the white-list, do nothing + return NO; +} + +- (NSString*)errorStringForURL:(NSURL*)url +{ + return [NSString stringWithFormat:self.whitelistRejectionFormatString, [url absoluteString]]; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.h b/_platforms/ios/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.h new file mode 100644 index 000000000..9be2be2dc --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.h @@ -0,0 +1,35 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import + +@interface NSDictionary (CordovaPreferences) + +- (id)cordovaSettingForKey:(NSString*)key; +- (BOOL)cordovaBoolSettingForKey:(NSString*)key defaultValue:(BOOL)defaultValue; +- (CGFloat)cordovaFloatSettingForKey:(NSString*)key defaultValue:(CGFloat)defaultValue; + +@end + +@interface NSMutableDictionary (CordovaPreferences) + +- (void)setCordovaSetting:(id)value forKey:(NSString*)key; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m b/_platforms/ios/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m new file mode 100644 index 000000000..1266ba20f --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m @@ -0,0 +1,92 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "NSDictionary+CordovaPreferences.h" +#import + +@implementation NSDictionary (CordovaPreferences) + +- (id)cordovaSettingForKey:(NSString*)key +{ + return [self objectForKey:[key lowercaseString]]; +} + +- (BOOL)cordovaBoolSettingForKey:(NSString*)key defaultValue:(BOOL)defaultValue +{ + BOOL value = defaultValue; + id prefObj = [self cordovaSettingForKey:key]; + + if (prefObj == nil) { + NSLog(@"The preference key \"%@\" is not defined and will default to \"%@\"", + key, + (defaultValue ? @"TRUE" : @"FALSE")); + + return value; + } + + if ([prefObj isKindOfClass:NSString.class]) { + prefObj = [prefObj lowercaseString]; + + if ( + // True Case + [prefObj isEqualToString:@"true"] || + [prefObj isEqualToString:@"1"] || + // False Case + [prefObj isEqualToString:@"false"] || + [prefObj isEqualToString:@"0"] + ) + { + value = [prefObj isEqualToString:@"true"] || [prefObj isEqualToString:@"1"]; + } + } else if ( + [prefObj isKindOfClass:NSNumber.class] && + ( + [prefObj isEqual: @YES] || + [prefObj isEqual: @NO] + ) + ) + { + value = [prefObj isEqual: @YES]; + } + + return value; +} + +- (CGFloat)cordovaFloatSettingForKey:(NSString*)key defaultValue:(CGFloat)defaultValue +{ + CGFloat value = defaultValue; + id prefObj = [self cordovaSettingForKey:key]; + + if (prefObj != nil) { + value = [prefObj floatValue]; + } + + return value; +} + +@end + +@implementation NSMutableDictionary (CordovaPreferences) + +- (void)setCordovaSetting:(id)value forKey:(NSString*)key +{ + [self setObject:value forKey:[key lowercaseString]]; +} + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.h b/_platforms/ios/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.h new file mode 100644 index 000000000..79e65164e --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.h @@ -0,0 +1,29 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface NSMutableArray (QueueAdditions) + +- (id)cdv_pop; +- (id)cdv_queueHead; +- (id)cdv_dequeue; +- (void)cdv_enqueue:(id)obj; + +@end diff --git a/_platforms/ios/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.m b/_platforms/ios/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.m new file mode 100644 index 000000000..2b3acdc0f --- /dev/null +++ b/_platforms/ios/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.m @@ -0,0 +1,58 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "NSMutableArray+QueueAdditions.h" + +@implementation NSMutableArray (QueueAdditions) + +- (id)cdv_queueHead +{ + if ([self count] == 0) { + return nil; + } + + return [self objectAtIndex:0]; +} + +- (__autoreleasing id)cdv_dequeue +{ + if ([self count] == 0) { + return nil; + } + + id head = [self objectAtIndex:0]; + if (head != nil) { + // [[head retain] autorelease]; ARC - the __autoreleasing on the return value should so the same thing + [self removeObjectAtIndex:0]; + } + + return head; +} + +- (id)cdv_pop +{ + return [self cdv_dequeue]; +} + +- (void)cdv_enqueue:(id)object +{ + [self addObject:object]; +} + +@end diff --git a/_platforms/ios/CordovaLib/CordovaLib.xcodeproj/project.pbxproj b/_platforms/ios/CordovaLib/CordovaLib.xcodeproj/project.pbxproj new file mode 100644 index 000000000..f0958e478 --- /dev/null +++ b/_platforms/ios/CordovaLib/CordovaLib.xcodeproj/project.pbxproj @@ -0,0 +1,803 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 28BFF9141F355A4E00DDF01A /* CDVLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 28BFF9121F355A4E00DDF01A /* CDVLogger.h */; }; + 28BFF9151F355A4E00DDF01A /* CDVLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 28BFF9131F355A4E00DDF01A /* CDVLogger.m */; }; + 2F4D42BC23F218BA00501999 /* CDVURLSchemeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F4D42BA23F218BA00501999 /* CDVURLSchemeHandler.h */; }; + 2F4D42BD23F218BA00501999 /* CDVURLSchemeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4D42BB23F218BA00501999 /* CDVURLSchemeHandler.m */; }; + 2FCCEA17247E7366007276A8 /* CDVLaunchScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E714D3423F535B500A321AF /* CDVLaunchScreen.m */; }; + 2FCCEA18247E7366007276A8 /* CDVLaunchScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E714D3223F535B500A321AF /* CDVLaunchScreen.h */; }; + 3093E2231B16D6A3003F381A /* CDVIntentAndNavigationFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3093E2211B16D6A3003F381A /* CDVIntentAndNavigationFilter.h */; }; + 3093E2241B16D6A3003F381A /* CDVIntentAndNavigationFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 3093E2221B16D6A3003F381A /* CDVIntentAndNavigationFilter.m */; }; + 4E23F8FB23E16E96006CD852 /* CDVWebViewProcessPoolFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E23F8F523E16E96006CD852 /* CDVWebViewProcessPoolFactory.m */; }; + 4E23F8FC23E16E96006CD852 /* CDVWebViewUIDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E23F8F623E16E96006CD852 /* CDVWebViewUIDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4E23F8FD23E16E96006CD852 /* CDVWebViewUIDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E23F8F723E16E96006CD852 /* CDVWebViewUIDelegate.m */; }; + 4E23F8FE23E16E96006CD852 /* CDVWebViewEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E23F8F823E16E96006CD852 /* CDVWebViewEngine.m */; }; + 4E23F8FF23E16E96006CD852 /* CDVWebViewProcessPoolFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E23F8F923E16E96006CD852 /* CDVWebViewProcessPoolFactory.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4E23F90023E16E96006CD852 /* CDVWebViewEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E23F8FA23E16E96006CD852 /* CDVWebViewEngine.h */; }; + 4E23F90323E17FFA006CD852 /* CDVWebViewUIDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E23F8F623E16E96006CD852 /* CDVWebViewUIDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4E714D3623F535B500A321AF /* CDVLaunchScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E714D3223F535B500A321AF /* CDVLaunchScreen.h */; }; + 4E714D3823F535B500A321AF /* CDVLaunchScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E714D3423F535B500A321AF /* CDVLaunchScreen.m */; }; + 4F56D82D254A2EB50063F1D6 /* CDVWebViewEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E23F8F823E16E96006CD852 /* CDVWebViewEngine.m */; }; + 4F56D830254A2ED70063F1D6 /* CDVWebViewUIDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E23F8F723E16E96006CD852 /* CDVWebViewUIDelegate.m */; }; + 4F56D833254A2ED90063F1D6 /* CDVWebViewProcessPoolFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E23F8F523E16E96006CD852 /* CDVWebViewProcessPoolFactory.m */; }; + 4F56D836254A2EE10063F1D6 /* CDVWebViewEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E23F8FA23E16E96006CD852 /* CDVWebViewEngine.h */; }; + 4F56D839254A2EE40063F1D6 /* CDVWebViewProcessPoolFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E23F8F923E16E96006CD852 /* CDVWebViewProcessPoolFactory.h */; }; + 4F56D83C254A2F2F0063F1D6 /* CDVURLSchemeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4D42BB23F218BA00501999 /* CDVURLSchemeHandler.m */; }; + 7E7F69B91ABA3692007546F4 /* CDVHandleOpenURL.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CF81AB9028C008C4574 /* CDVHandleOpenURL.h */; }; + 7ED95D021AB9028C008C4574 /* CDVDebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CF21AB9028C008C4574 /* CDVDebug.h */; }; + 7ED95D031AB9028C008C4574 /* CDVJSON_private.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CF31AB9028C008C4574 /* CDVJSON_private.h */; }; + 7ED95D041AB9028C008C4574 /* CDVJSON_private.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95CF41AB9028C008C4574 /* CDVJSON_private.m */; }; + 7ED95D051AB9028C008C4574 /* CDVPlugin+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CF51AB9028C008C4574 /* CDVPlugin+Private.h */; }; + 7ED95D071AB9028C008C4574 /* CDVHandleOpenURL.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95CF91AB9028C008C4574 /* CDVHandleOpenURL.m */; }; + 7ED95D351AB9029B008C4574 /* CDV.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D0F1AB9029B008C4574 /* CDV.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D361AB9029B008C4574 /* CDVAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D101AB9029B008C4574 /* CDVAppDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D371AB9029B008C4574 /* CDVAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D111AB9029B008C4574 /* CDVAppDelegate.m */; }; + 7ED95D381AB9029B008C4574 /* CDVAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D121AB9029B008C4574 /* CDVAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D391AB9029B008C4574 /* CDVAvailabilityDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D131AB9029B008C4574 /* CDVAvailabilityDeprecated.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D3A1AB9029B008C4574 /* CDVCommandDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D141AB9029B008C4574 /* CDVCommandDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D3B1AB9029B008C4574 /* CDVCommandDelegateImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D151AB9029B008C4574 /* CDVCommandDelegateImpl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D3C1AB9029B008C4574 /* CDVCommandDelegateImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D161AB9029B008C4574 /* CDVCommandDelegateImpl.m */; }; + 7ED95D3D1AB9029B008C4574 /* CDVCommandQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D171AB9029B008C4574 /* CDVCommandQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D3E1AB9029B008C4574 /* CDVCommandQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D181AB9029B008C4574 /* CDVCommandQueue.m */; }; + 7ED95D3F1AB9029B008C4574 /* CDVConfigParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D191AB9029B008C4574 /* CDVConfigParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D401AB9029B008C4574 /* CDVConfigParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D1A1AB9029B008C4574 /* CDVConfigParser.m */; }; + 7ED95D411AB9029B008C4574 /* CDVInvokedUrlCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D1B1AB9029B008C4574 /* CDVInvokedUrlCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D421AB9029B008C4574 /* CDVInvokedUrlCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D1C1AB9029B008C4574 /* CDVInvokedUrlCommand.m */; }; + 7ED95D431AB9029B008C4574 /* CDVPlugin+Resources.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D1D1AB9029B008C4574 /* CDVPlugin+Resources.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D441AB9029B008C4574 /* CDVPlugin+Resources.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D1E1AB9029B008C4574 /* CDVPlugin+Resources.m */; }; + 7ED95D451AB9029B008C4574 /* CDVPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D1F1AB9029B008C4574 /* CDVPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D461AB9029B008C4574 /* CDVPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D201AB9029B008C4574 /* CDVPlugin.m */; }; + 7ED95D471AB9029B008C4574 /* CDVPluginResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D211AB9029B008C4574 /* CDVPluginResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D481AB9029B008C4574 /* CDVPluginResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D221AB9029B008C4574 /* CDVPluginResult.m */; }; + 7ED95D491AB9029B008C4574 /* CDVScreenOrientationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D231AB9029B008C4574 /* CDVScreenOrientationDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D4A1AB9029B008C4574 /* CDVTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D241AB9029B008C4574 /* CDVTimer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D4B1AB9029B008C4574 /* CDVTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D251AB9029B008C4574 /* CDVTimer.m */; }; + 7ED95D501AB9029B008C4574 /* CDVViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D2A1AB9029B008C4574 /* CDVViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D511AB9029B008C4574 /* CDVViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D2B1AB9029B008C4574 /* CDVViewController.m */; }; + 7ED95D521AB9029B008C4574 /* CDVWebViewEngineProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D2C1AB9029B008C4574 /* CDVWebViewEngineProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D531AB9029B008C4574 /* CDVWhitelist.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D2D1AB9029B008C4574 /* CDVWhitelist.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D541AB9029B008C4574 /* CDVWhitelist.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D2E1AB9029B008C4574 /* CDVWhitelist.m */; }; + 7ED95D571AB9029B008C4574 /* NSDictionary+CordovaPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D311AB9029B008C4574 /* NSDictionary+CordovaPreferences.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D581AB9029B008C4574 /* NSDictionary+CordovaPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D321AB9029B008C4574 /* NSDictionary+CordovaPreferences.m */; }; + 7ED95D591AB9029B008C4574 /* NSMutableArray+QueueAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D331AB9029B008C4574 /* NSMutableArray+QueueAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ED95D5A1AB9029B008C4574 /* NSMutableArray+QueueAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D341AB9029B008C4574 /* NSMutableArray+QueueAdditions.m */; }; + 9052DE712150D040008E83D4 /* CDVAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D111AB9029B008C4574 /* CDVAppDelegate.m */; }; + 9052DE722150D040008E83D4 /* CDVCommandDelegateImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D161AB9029B008C4574 /* CDVCommandDelegateImpl.m */; }; + 9052DE732150D040008E83D4 /* CDVCommandQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D181AB9029B008C4574 /* CDVCommandQueue.m */; }; + 9052DE742150D040008E83D4 /* CDVConfigParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D1A1AB9029B008C4574 /* CDVConfigParser.m */; }; + 9052DE752150D040008E83D4 /* CDVInvokedUrlCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D1C1AB9029B008C4574 /* CDVInvokedUrlCommand.m */; }; + 9052DE762150D040008E83D4 /* CDVPlugin+Resources.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D1E1AB9029B008C4574 /* CDVPlugin+Resources.m */; }; + 9052DE772150D040008E83D4 /* CDVPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D201AB9029B008C4574 /* CDVPlugin.m */; }; + 9052DE782150D040008E83D4 /* CDVPluginResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D221AB9029B008C4574 /* CDVPluginResult.m */; }; + 9052DE792150D040008E83D4 /* CDVTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D251AB9029B008C4574 /* CDVTimer.m */; }; + 9052DE7C2150D040008E83D4 /* CDVViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D2B1AB9029B008C4574 /* CDVViewController.m */; }; + 9052DE7D2150D040008E83D4 /* CDVWhitelist.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D2E1AB9029B008C4574 /* CDVWhitelist.m */; }; + 9052DE7E2150D040008E83D4 /* NSDictionary+CordovaPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D321AB9029B008C4574 /* NSDictionary+CordovaPreferences.m */; }; + 9052DE7F2150D040008E83D4 /* NSMutableArray+QueueAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95D341AB9029B008C4574 /* NSMutableArray+QueueAdditions.m */; }; + 9052DE802150D040008E83D4 /* CDVJSON_private.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95CF41AB9028C008C4574 /* CDVJSON_private.m */; }; + 9052DE812150D040008E83D4 /* CDVLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 28BFF9131F355A4E00DDF01A /* CDVLogger.m */; }; + 9052DE822150D040008E83D4 /* CDVGestureHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A3B082D31BB15CEA00D8DC35 /* CDVGestureHandler.m */; }; + 9052DE832150D040008E83D4 /* CDVIntentAndNavigationFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 3093E2221B16D6A3003F381A /* CDVIntentAndNavigationFilter.m */; }; + 9052DE842150D040008E83D4 /* CDVHandleOpenURL.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ED95CF91AB9028C008C4574 /* CDVHandleOpenURL.m */; }; + 9052DE892150D06B008E83D4 /* CDVDebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CF21AB9028C008C4574 /* CDVDebug.h */; }; + 9052DE8A2150D06B008E83D4 /* CDVJSON_private.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CF31AB9028C008C4574 /* CDVJSON_private.h */; }; + 9052DE8B2150D06B008E83D4 /* CDVPlugin+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CF51AB9028C008C4574 /* CDVPlugin+Private.h */; }; + 9052DE8C2150D06B008E83D4 /* CDVLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 28BFF9121F355A4E00DDF01A /* CDVLogger.h */; }; + 9052DE8D2150D06B008E83D4 /* CDVGestureHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = A3B082D21BB15CEA00D8DC35 /* CDVGestureHandler.h */; }; + 9052DE8E2150D06B008E83D4 /* CDVIntentAndNavigationFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3093E2211B16D6A3003F381A /* CDVIntentAndNavigationFilter.h */; }; + 9052DE8F2150D06B008E83D4 /* CDVHandleOpenURL.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CF81AB9028C008C4574 /* CDVHandleOpenURL.h */; }; + A3B082D41BB15CEA00D8DC35 /* CDVGestureHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = A3B082D21BB15CEA00D8DC35 /* CDVGestureHandler.h */; }; + A3B082D51BB15CEA00D8DC35 /* CDVGestureHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A3B082D31BB15CEA00D8DC35 /* CDVGestureHandler.m */; }; + C0C01EB61E3911D50056E6CB /* Cordova.h in Headers */ = {isa = PBXBuildFile; fileRef = C0C01EB41E3911D50056E6CB /* Cordova.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EBB1E39131A0056E6CB /* CDV.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D0F1AB9029B008C4574 /* CDV.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EBC1E39131A0056E6CB /* CDVAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D101AB9029B008C4574 /* CDVAppDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EBD1E39131A0056E6CB /* CDVAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D121AB9029B008C4574 /* CDVAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EBE1E39131A0056E6CB /* CDVAvailabilityDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D131AB9029B008C4574 /* CDVAvailabilityDeprecated.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EBF1E39131A0056E6CB /* CDVCommandDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D141AB9029B008C4574 /* CDVCommandDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EC01E39131A0056E6CB /* CDVCommandDelegateImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D151AB9029B008C4574 /* CDVCommandDelegateImpl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EC11E39131A0056E6CB /* CDVCommandQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D171AB9029B008C4574 /* CDVCommandQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EC21E39131A0056E6CB /* CDVConfigParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D191AB9029B008C4574 /* CDVConfigParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EC31E39131A0056E6CB /* CDVInvokedUrlCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D1B1AB9029B008C4574 /* CDVInvokedUrlCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EC41E39131A0056E6CB /* CDVPlugin+Resources.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D1D1AB9029B008C4574 /* CDVPlugin+Resources.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EC51E39131A0056E6CB /* CDVPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D1F1AB9029B008C4574 /* CDVPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EC61E39131A0056E6CB /* CDVPluginResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D211AB9029B008C4574 /* CDVPluginResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EC71E39131A0056E6CB /* CDVScreenOrientationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D231AB9029B008C4574 /* CDVScreenOrientationDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01EC81E39131A0056E6CB /* CDVTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D241AB9029B008C4574 /* CDVTimer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01ECB1E39131A0056E6CB /* CDVViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D2A1AB9029B008C4574 /* CDVViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01ECC1E39131A0056E6CB /* CDVWebViewEngineProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D2C1AB9029B008C4574 /* CDVWebViewEngineProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01ECD1E39131A0056E6CB /* CDVWhitelist.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D2D1AB9029B008C4574 /* CDVWhitelist.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01ECE1E39131A0056E6CB /* NSDictionary+CordovaPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D311AB9029B008C4574 /* NSDictionary+CordovaPreferences.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0C01ECF1E39131A0056E6CB /* NSMutableArray+QueueAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D331AB9029B008C4574 /* NSMutableArray+QueueAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 28BFF9121F355A4E00DDF01A /* CDVLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVLogger.h; sourceTree = ""; }; + 28BFF9131F355A4E00DDF01A /* CDVLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVLogger.m; sourceTree = ""; }; + 2F4D42BA23F218BA00501999 /* CDVURLSchemeHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CDVURLSchemeHandler.h; sourceTree = ""; }; + 2F4D42BB23F218BA00501999 /* CDVURLSchemeHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CDVURLSchemeHandler.m; sourceTree = ""; }; + 3093E2211B16D6A3003F381A /* CDVIntentAndNavigationFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVIntentAndNavigationFilter.h; sourceTree = ""; }; + 3093E2221B16D6A3003F381A /* CDVIntentAndNavigationFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVIntentAndNavigationFilter.m; sourceTree = ""; }; + 4E23F8F523E16E96006CD852 /* CDVWebViewProcessPoolFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVWebViewProcessPoolFactory.m; sourceTree = ""; }; + 4E23F8F623E16E96006CD852 /* CDVWebViewUIDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVWebViewUIDelegate.h; sourceTree = ""; }; + 4E23F8F723E16E96006CD852 /* CDVWebViewUIDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVWebViewUIDelegate.m; sourceTree = ""; }; + 4E23F8F823E16E96006CD852 /* CDVWebViewEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVWebViewEngine.m; sourceTree = ""; }; + 4E23F8F923E16E96006CD852 /* CDVWebViewProcessPoolFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVWebViewProcessPoolFactory.h; sourceTree = ""; }; + 4E23F8FA23E16E96006CD852 /* CDVWebViewEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVWebViewEngine.h; sourceTree = ""; }; + 4E714D3223F535B500A321AF /* CDVLaunchScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVLaunchScreen.h; sourceTree = ""; }; + 4E714D3423F535B500A321AF /* CDVLaunchScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVLaunchScreen.m; sourceTree = ""; }; + 68A32D7114102E1C006B237C /* libCordova.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCordova.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7ED95CF21AB9028C008C4574 /* CDVDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVDebug.h; sourceTree = ""; }; + 7ED95CF31AB9028C008C4574 /* CDVJSON_private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVJSON_private.h; sourceTree = ""; }; + 7ED95CF41AB9028C008C4574 /* CDVJSON_private.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVJSON_private.m; sourceTree = ""; }; + 7ED95CF51AB9028C008C4574 /* CDVPlugin+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CDVPlugin+Private.h"; sourceTree = ""; }; + 7ED95CF81AB9028C008C4574 /* CDVHandleOpenURL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVHandleOpenURL.h; sourceTree = ""; }; + 7ED95CF91AB9028C008C4574 /* CDVHandleOpenURL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVHandleOpenURL.m; sourceTree = ""; }; + 7ED95D0F1AB9029B008C4574 /* CDV.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDV.h; sourceTree = ""; }; + 7ED95D101AB9029B008C4574 /* CDVAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVAppDelegate.h; sourceTree = ""; }; + 7ED95D111AB9029B008C4574 /* CDVAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVAppDelegate.m; sourceTree = ""; }; + 7ED95D121AB9029B008C4574 /* CDVAvailability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVAvailability.h; sourceTree = ""; }; + 7ED95D131AB9029B008C4574 /* CDVAvailabilityDeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVAvailabilityDeprecated.h; sourceTree = ""; }; + 7ED95D141AB9029B008C4574 /* CDVCommandDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVCommandDelegate.h; sourceTree = ""; }; + 7ED95D151AB9029B008C4574 /* CDVCommandDelegateImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVCommandDelegateImpl.h; sourceTree = ""; }; + 7ED95D161AB9029B008C4574 /* CDVCommandDelegateImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVCommandDelegateImpl.m; sourceTree = ""; }; + 7ED95D171AB9029B008C4574 /* CDVCommandQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVCommandQueue.h; sourceTree = ""; }; + 7ED95D181AB9029B008C4574 /* CDVCommandQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVCommandQueue.m; sourceTree = ""; }; + 7ED95D191AB9029B008C4574 /* CDVConfigParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVConfigParser.h; sourceTree = ""; }; + 7ED95D1A1AB9029B008C4574 /* CDVConfigParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVConfigParser.m; sourceTree = ""; }; + 7ED95D1B1AB9029B008C4574 /* CDVInvokedUrlCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVInvokedUrlCommand.h; sourceTree = ""; }; + 7ED95D1C1AB9029B008C4574 /* CDVInvokedUrlCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVInvokedUrlCommand.m; sourceTree = ""; }; + 7ED95D1D1AB9029B008C4574 /* CDVPlugin+Resources.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CDVPlugin+Resources.h"; sourceTree = ""; }; + 7ED95D1E1AB9029B008C4574 /* CDVPlugin+Resources.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CDVPlugin+Resources.m"; sourceTree = ""; }; + 7ED95D1F1AB9029B008C4574 /* CDVPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVPlugin.h; sourceTree = ""; }; + 7ED95D201AB9029B008C4574 /* CDVPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVPlugin.m; sourceTree = ""; }; + 7ED95D211AB9029B008C4574 /* CDVPluginResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVPluginResult.h; sourceTree = ""; }; + 7ED95D221AB9029B008C4574 /* CDVPluginResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVPluginResult.m; sourceTree = ""; }; + 7ED95D231AB9029B008C4574 /* CDVScreenOrientationDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVScreenOrientationDelegate.h; sourceTree = ""; }; + 7ED95D241AB9029B008C4574 /* CDVTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVTimer.h; sourceTree = ""; }; + 7ED95D251AB9029B008C4574 /* CDVTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVTimer.m; sourceTree = ""; }; + 7ED95D2A1AB9029B008C4574 /* CDVViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVViewController.h; sourceTree = ""; }; + 7ED95D2B1AB9029B008C4574 /* CDVViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVViewController.m; sourceTree = ""; }; + 7ED95D2C1AB9029B008C4574 /* CDVWebViewEngineProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVWebViewEngineProtocol.h; sourceTree = ""; }; + 7ED95D2D1AB9029B008C4574 /* CDVWhitelist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVWhitelist.h; sourceTree = ""; }; + 7ED95D2E1AB9029B008C4574 /* CDVWhitelist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVWhitelist.m; sourceTree = ""; }; + 7ED95D311AB9029B008C4574 /* NSDictionary+CordovaPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+CordovaPreferences.h"; sourceTree = ""; }; + 7ED95D321AB9029B008C4574 /* NSDictionary+CordovaPreferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+CordovaPreferences.m"; sourceTree = ""; }; + 7ED95D331AB9029B008C4574 /* NSMutableArray+QueueAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+QueueAdditions.h"; sourceTree = ""; }; + 7ED95D341AB9029B008C4574 /* NSMutableArray+QueueAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+QueueAdditions.m"; sourceTree = ""; }; + A3B082D21BB15CEA00D8DC35 /* CDVGestureHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVGestureHandler.h; sourceTree = ""; }; + A3B082D31BB15CEA00D8DC35 /* CDVGestureHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVGestureHandler.m; sourceTree = ""; }; + AA747D9E0F9514B9006C5449 /* CordovaLib_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CordovaLib_Prefix.pch; sourceTree = SOURCE_ROOT; }; + C0C01EB21E3911D50056E6CB /* Cordova.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cordova.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C0C01EB41E3911D50056E6CB /* Cordova.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Cordova.h; sourceTree = ""; }; + C0C01EB51E3911D50056E6CB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C0C01EAE1E3911D50056E6CB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07C0554694100DB518D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + 68A32D7114102E1C006B237C /* libCordova.a */, + C0C01EB21E3911D50056E6CB /* Cordova.framework */, + ); + name = Products; + sourceTree = ""; + }; + 0867D691FE84028FC02AAC07 = { + isa = PBXGroup; + children = ( + 7ED95D0E1AB9029B008C4574 /* Public */, + 7ED95CF11AB9028C008C4574 /* Private */, + C0C01EB31E3911D50056E6CB /* Cordova */, + 034768DFFF38A50411DB9C8B /* Products */, + ); + sourceTree = ""; + }; + 28BFF9111F355A1D00DDF01A /* CDVLogger */ = { + isa = PBXGroup; + children = ( + 28BFF9121F355A4E00DDF01A /* CDVLogger.h */, + 28BFF9131F355A4E00DDF01A /* CDVLogger.m */, + ); + path = CDVLogger; + sourceTree = ""; + }; + 3093E2201B16D6A3003F381A /* CDVIntentAndNavigationFilter */ = { + isa = PBXGroup; + children = ( + 3093E2211B16D6A3003F381A /* CDVIntentAndNavigationFilter.h */, + 3093E2221B16D6A3003F381A /* CDVIntentAndNavigationFilter.m */, + ); + path = CDVIntentAndNavigationFilter; + sourceTree = ""; + }; + 4E23F8F423E16D30006CD852 /* CDVWebViewEngine */ = { + isa = PBXGroup; + children = ( + 4E23F8FA23E16E96006CD852 /* CDVWebViewEngine.h */, + 4E23F8F823E16E96006CD852 /* CDVWebViewEngine.m */, + 4E23F8F623E16E96006CD852 /* CDVWebViewUIDelegate.h */, + 4E23F8F723E16E96006CD852 /* CDVWebViewUIDelegate.m */, + ); + path = CDVWebViewEngine; + sourceTree = ""; + }; + 4E714D3123F5356700A321AF /* CDVLaunchScreen */ = { + isa = PBXGroup; + children = ( + 4E714D3223F535B500A321AF /* CDVLaunchScreen.h */, + 4E714D3423F535B500A321AF /* CDVLaunchScreen.m */, + ); + path = CDVLaunchScreen; + sourceTree = ""; + }; + 7ED95CF11AB9028C008C4574 /* Private */ = { + isa = PBXGroup; + children = ( + AA747D9E0F9514B9006C5449 /* CordovaLib_Prefix.pch */, + 7ED95CF21AB9028C008C4574 /* CDVDebug.h */, + 7ED95CF31AB9028C008C4574 /* CDVJSON_private.h */, + 7ED95CF41AB9028C008C4574 /* CDVJSON_private.m */, + 7ED95CF51AB9028C008C4574 /* CDVPlugin+Private.h */, + 7ED95CF61AB9028C008C4574 /* Plugins */, + ); + name = Private; + path = Classes/Private; + sourceTree = ""; + }; + 7ED95CF61AB9028C008C4574 /* Plugins */ = { + isa = PBXGroup; + children = ( + 4E714D3123F5356700A321AF /* CDVLaunchScreen */, + 4E23F8F423E16D30006CD852 /* CDVWebViewEngine */, + 28BFF9111F355A1D00DDF01A /* CDVLogger */, + A3B082D11BB15CEA00D8DC35 /* CDVGestureHandler */, + 3093E2201B16D6A3003F381A /* CDVIntentAndNavigationFilter */, + 7ED95CF71AB9028C008C4574 /* CDVHandleOpenURL */, + ); + path = Plugins; + sourceTree = ""; + }; + 7ED95CF71AB9028C008C4574 /* CDVHandleOpenURL */ = { + isa = PBXGroup; + children = ( + 7ED95CF81AB9028C008C4574 /* CDVHandleOpenURL.h */, + 7ED95CF91AB9028C008C4574 /* CDVHandleOpenURL.m */, + ); + path = CDVHandleOpenURL; + sourceTree = ""; + }; + 7ED95D0E1AB9029B008C4574 /* Public */ = { + isa = PBXGroup; + children = ( + 7ED95D0F1AB9029B008C4574 /* CDV.h */, + 7ED95D101AB9029B008C4574 /* CDVAppDelegate.h */, + 7ED95D111AB9029B008C4574 /* CDVAppDelegate.m */, + 7ED95D121AB9029B008C4574 /* CDVAvailability.h */, + 7ED95D131AB9029B008C4574 /* CDVAvailabilityDeprecated.h */, + 7ED95D141AB9029B008C4574 /* CDVCommandDelegate.h */, + 7ED95D151AB9029B008C4574 /* CDVCommandDelegateImpl.h */, + 7ED95D161AB9029B008C4574 /* CDVCommandDelegateImpl.m */, + 7ED95D171AB9029B008C4574 /* CDVCommandQueue.h */, + 7ED95D181AB9029B008C4574 /* CDVCommandQueue.m */, + 7ED95D191AB9029B008C4574 /* CDVConfigParser.h */, + 7ED95D1A1AB9029B008C4574 /* CDVConfigParser.m */, + 7ED95D1B1AB9029B008C4574 /* CDVInvokedUrlCommand.h */, + 7ED95D1C1AB9029B008C4574 /* CDVInvokedUrlCommand.m */, + 7ED95D1D1AB9029B008C4574 /* CDVPlugin+Resources.h */, + 7ED95D1E1AB9029B008C4574 /* CDVPlugin+Resources.m */, + 7ED95D1F1AB9029B008C4574 /* CDVPlugin.h */, + 7ED95D201AB9029B008C4574 /* CDVPlugin.m */, + 7ED95D211AB9029B008C4574 /* CDVPluginResult.h */, + 7ED95D221AB9029B008C4574 /* CDVPluginResult.m */, + 7ED95D231AB9029B008C4574 /* CDVScreenOrientationDelegate.h */, + 7ED95D241AB9029B008C4574 /* CDVTimer.h */, + 7ED95D251AB9029B008C4574 /* CDVTimer.m */, + 7ED95D2A1AB9029B008C4574 /* CDVViewController.h */, + 4E23F8F923E16E96006CD852 /* CDVWebViewProcessPoolFactory.h */, + 4E23F8F523E16E96006CD852 /* CDVWebViewProcessPoolFactory.m */, + 7ED95D2B1AB9029B008C4574 /* CDVViewController.m */, + 7ED95D2C1AB9029B008C4574 /* CDVWebViewEngineProtocol.h */, + 7ED95D2D1AB9029B008C4574 /* CDVWhitelist.h */, + 7ED95D2E1AB9029B008C4574 /* CDVWhitelist.m */, + 7ED95D311AB9029B008C4574 /* NSDictionary+CordovaPreferences.h */, + 7ED95D321AB9029B008C4574 /* NSDictionary+CordovaPreferences.m */, + 7ED95D331AB9029B008C4574 /* NSMutableArray+QueueAdditions.h */, + 7ED95D341AB9029B008C4574 /* NSMutableArray+QueueAdditions.m */, + 2F4D42BA23F218BA00501999 /* CDVURLSchemeHandler.h */, + 2F4D42BB23F218BA00501999 /* CDVURLSchemeHandler.m */, + ); + name = Public; + path = Classes/Public; + sourceTree = ""; + }; + A3B082D11BB15CEA00D8DC35 /* CDVGestureHandler */ = { + isa = PBXGroup; + children = ( + A3B082D21BB15CEA00D8DC35 /* CDVGestureHandler.h */, + A3B082D31BB15CEA00D8DC35 /* CDVGestureHandler.m */, + ); + path = CDVGestureHandler; + sourceTree = ""; + }; + C0C01EB31E3911D50056E6CB /* Cordova */ = { + isa = PBXGroup; + children = ( + C0C01EB41E3911D50056E6CB /* Cordova.h */, + C0C01EB51E3911D50056E6CB /* Info.plist */, + ); + path = Cordova; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + C0C01EAF1E3911D50056E6CB /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C0C01EB61E3911D50056E6CB /* Cordova.h in Headers */, + C0C01EBB1E39131A0056E6CB /* CDV.h in Headers */, + C0C01EBC1E39131A0056E6CB /* CDVAppDelegate.h in Headers */, + C0C01EBD1E39131A0056E6CB /* CDVAvailability.h in Headers */, + C0C01EBE1E39131A0056E6CB /* CDVAvailabilityDeprecated.h in Headers */, + C0C01EBF1E39131A0056E6CB /* CDVCommandDelegate.h in Headers */, + C0C01EC01E39131A0056E6CB /* CDVCommandDelegateImpl.h in Headers */, + 4F56D839254A2EE40063F1D6 /* CDVWebViewProcessPoolFactory.h in Headers */, + C0C01EC11E39131A0056E6CB /* CDVCommandQueue.h in Headers */, + C0C01EC21E39131A0056E6CB /* CDVConfigParser.h in Headers */, + C0C01EC31E39131A0056E6CB /* CDVInvokedUrlCommand.h in Headers */, + C0C01EC41E39131A0056E6CB /* CDVPlugin+Resources.h in Headers */, + C0C01EC51E39131A0056E6CB /* CDVPlugin.h in Headers */, + C0C01EC61E39131A0056E6CB /* CDVPluginResult.h in Headers */, + C0C01EC71E39131A0056E6CB /* CDVScreenOrientationDelegate.h in Headers */, + 4F56D836254A2EE10063F1D6 /* CDVWebViewEngine.h in Headers */, + C0C01EC81E39131A0056E6CB /* CDVTimer.h in Headers */, + C0C01ECB1E39131A0056E6CB /* CDVViewController.h in Headers */, + C0C01ECC1E39131A0056E6CB /* CDVWebViewEngineProtocol.h in Headers */, + C0C01ECD1E39131A0056E6CB /* CDVWhitelist.h in Headers */, + C0C01ECE1E39131A0056E6CB /* NSDictionary+CordovaPreferences.h in Headers */, + 4E23F90323E17FFA006CD852 /* CDVWebViewUIDelegate.h in Headers */, + C0C01ECF1E39131A0056E6CB /* NSMutableArray+QueueAdditions.h in Headers */, + 9052DE892150D06B008E83D4 /* CDVDebug.h in Headers */, + 9052DE8A2150D06B008E83D4 /* CDVJSON_private.h in Headers */, + 9052DE8B2150D06B008E83D4 /* CDVPlugin+Private.h in Headers */, + 9052DE8C2150D06B008E83D4 /* CDVLogger.h in Headers */, + 9052DE8D2150D06B008E83D4 /* CDVGestureHandler.h in Headers */, + 9052DE8E2150D06B008E83D4 /* CDVIntentAndNavigationFilter.h in Headers */, + 9052DE8F2150D06B008E83D4 /* CDVHandleOpenURL.h in Headers */, + 2FCCEA18247E7366007276A8 /* CDVLaunchScreen.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07A0554694100DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 7ED95D351AB9029B008C4574 /* CDV.h in Headers */, + 7ED95D361AB9029B008C4574 /* CDVAppDelegate.h in Headers */, + 7ED95D381AB9029B008C4574 /* CDVAvailability.h in Headers */, + 7ED95D391AB9029B008C4574 /* CDVAvailabilityDeprecated.h in Headers */, + 7ED95D3A1AB9029B008C4574 /* CDVCommandDelegate.h in Headers */, + 7ED95D3B1AB9029B008C4574 /* CDVCommandDelegateImpl.h in Headers */, + 7ED95D3D1AB9029B008C4574 /* CDVCommandQueue.h in Headers */, + 4E23F8FF23E16E96006CD852 /* CDVWebViewProcessPoolFactory.h in Headers */, + 7ED95D3F1AB9029B008C4574 /* CDVConfigParser.h in Headers */, + 2F4D42BC23F218BA00501999 /* CDVURLSchemeHandler.h in Headers */, + 7ED95D411AB9029B008C4574 /* CDVInvokedUrlCommand.h in Headers */, + 7ED95D431AB9029B008C4574 /* CDVPlugin+Resources.h in Headers */, + 7ED95D451AB9029B008C4574 /* CDVPlugin.h in Headers */, + 7ED95D471AB9029B008C4574 /* CDVPluginResult.h in Headers */, + 7ED95D491AB9029B008C4574 /* CDVScreenOrientationDelegate.h in Headers */, + 4E23F8FC23E16E96006CD852 /* CDVWebViewUIDelegate.h in Headers */, + 7ED95D4A1AB9029B008C4574 /* CDVTimer.h in Headers */, + 7ED95D501AB9029B008C4574 /* CDVViewController.h in Headers */, + 7ED95D521AB9029B008C4574 /* CDVWebViewEngineProtocol.h in Headers */, + 4E23F90023E16E96006CD852 /* CDVWebViewEngine.h in Headers */, + 7ED95D531AB9029B008C4574 /* CDVWhitelist.h in Headers */, + 7ED95D571AB9029B008C4574 /* NSDictionary+CordovaPreferences.h in Headers */, + 7ED95D591AB9029B008C4574 /* NSMutableArray+QueueAdditions.h in Headers */, + 7ED95D021AB9028C008C4574 /* CDVDebug.h in Headers */, + 7ED95D031AB9028C008C4574 /* CDVJSON_private.h in Headers */, + 7ED95D051AB9028C008C4574 /* CDVPlugin+Private.h in Headers */, + 28BFF9141F355A4E00DDF01A /* CDVLogger.h in Headers */, + A3B082D41BB15CEA00D8DC35 /* CDVGestureHandler.h in Headers */, + 3093E2231B16D6A3003F381A /* CDVIntentAndNavigationFilter.h in Headers */, + 7E7F69B91ABA3692007546F4 /* CDVHandleOpenURL.h in Headers */, + 4E714D3623F535B500A321AF /* CDVLaunchScreen.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + C0C01EB11E3911D50056E6CB /* Cordova */ = { + isa = PBXNativeTarget; + buildConfigurationList = C0C01EB91E3911D50056E6CB /* Build configuration list for PBXNativeTarget "Cordova" */; + buildPhases = ( + C0C01EAD1E3911D50056E6CB /* Sources */, + C0C01EAE1E3911D50056E6CB /* Frameworks */, + C0C01EAF1E3911D50056E6CB /* Headers */, + CEDDBB5523948D4C00506451 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Cordova; + productName = Cordova; + productReference = C0C01EB21E3911D50056E6CB /* Cordova.framework */; + productType = "com.apple.product-type.framework"; + }; + D2AAC07D0554694100DB518D /* CordovaLib */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "CordovaLib" */; + buildPhases = ( + D2AAC07A0554694100DB518D /* Headers */, + D2AAC07B0554694100DB518D /* Sources */, + D2AAC07C0554694100DB518D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CordovaLib; + productName = CordovaLib; + productReference = 68A32D7114102E1C006B237C /* libCordova.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1010; + TargetAttributes = { + C0C01EB11E3911D50056E6CB = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + }; + D2AAC07D0554694100DB518D = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "CordovaLib" */; + compatibilityVersion = "Xcode 11.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 0867D691FE84028FC02AAC07; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2AAC07D0554694100DB518D /* CordovaLib */, + C0C01EB11E3911D50056E6CB /* Cordova */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + CEDDBB5523948D4C00506451 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PROJECT_DIR}/../update_podspec.sh\" -s cordova\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C0C01EAD1E3911D50056E6CB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9052DE712150D040008E83D4 /* CDVAppDelegate.m in Sources */, + 9052DE722150D040008E83D4 /* CDVCommandDelegateImpl.m in Sources */, + 9052DE732150D040008E83D4 /* CDVCommandQueue.m in Sources */, + 9052DE742150D040008E83D4 /* CDVConfigParser.m in Sources */, + 9052DE752150D040008E83D4 /* CDVInvokedUrlCommand.m in Sources */, + 9052DE762150D040008E83D4 /* CDVPlugin+Resources.m in Sources */, + 9052DE772150D040008E83D4 /* CDVPlugin.m in Sources */, + 9052DE782150D040008E83D4 /* CDVPluginResult.m in Sources */, + 4F56D830254A2ED70063F1D6 /* CDVWebViewUIDelegate.m in Sources */, + 9052DE792150D040008E83D4 /* CDVTimer.m in Sources */, + 4F56D833254A2ED90063F1D6 /* CDVWebViewProcessPoolFactory.m in Sources */, + 4F56D82D254A2EB50063F1D6 /* CDVWebViewEngine.m in Sources */, + 9052DE7C2150D040008E83D4 /* CDVViewController.m in Sources */, + 4F56D83C254A2F2F0063F1D6 /* CDVURLSchemeHandler.m in Sources */, + 9052DE7D2150D040008E83D4 /* CDVWhitelist.m in Sources */, + 9052DE7E2150D040008E83D4 /* NSDictionary+CordovaPreferences.m in Sources */, + 9052DE7F2150D040008E83D4 /* NSMutableArray+QueueAdditions.m in Sources */, + 9052DE802150D040008E83D4 /* CDVJSON_private.m in Sources */, + 9052DE812150D040008E83D4 /* CDVLogger.m in Sources */, + 9052DE822150D040008E83D4 /* CDVGestureHandler.m in Sources */, + 9052DE832150D040008E83D4 /* CDVIntentAndNavigationFilter.m in Sources */, + 9052DE842150D040008E83D4 /* CDVHandleOpenURL.m in Sources */, + 2FCCEA17247E7366007276A8 /* CDVLaunchScreen.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07B0554694100DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7ED95D371AB9029B008C4574 /* CDVAppDelegate.m in Sources */, + 7ED95D3C1AB9029B008C4574 /* CDVCommandDelegateImpl.m in Sources */, + 7ED95D3E1AB9029B008C4574 /* CDVCommandQueue.m in Sources */, + 7ED95D401AB9029B008C4574 /* CDVConfigParser.m in Sources */, + 7ED95D421AB9029B008C4574 /* CDVInvokedUrlCommand.m in Sources */, + 7ED95D441AB9029B008C4574 /* CDVPlugin+Resources.m in Sources */, + 2F4D42BD23F218BA00501999 /* CDVURLSchemeHandler.m in Sources */, + 7ED95D461AB9029B008C4574 /* CDVPlugin.m in Sources */, + 7ED95D481AB9029B008C4574 /* CDVPluginResult.m in Sources */, + 7ED95D4B1AB9029B008C4574 /* CDVTimer.m in Sources */, + 4E23F8FE23E16E96006CD852 /* CDVWebViewEngine.m in Sources */, + 4E23F8FB23E16E96006CD852 /* CDVWebViewProcessPoolFactory.m in Sources */, + 7ED95D511AB9029B008C4574 /* CDVViewController.m in Sources */, + 7ED95D541AB9029B008C4574 /* CDVWhitelist.m in Sources */, + 7ED95D581AB9029B008C4574 /* NSDictionary+CordovaPreferences.m in Sources */, + 7ED95D5A1AB9029B008C4574 /* NSMutableArray+QueueAdditions.m in Sources */, + 7ED95D041AB9028C008C4574 /* CDVJSON_private.m in Sources */, + 4E23F8FD23E16E96006CD852 /* CDVWebViewUIDelegate.m in Sources */, + 28BFF9151F355A4E00DDF01A /* CDVLogger.m in Sources */, + A3B082D51BB15CEA00D8DC35 /* CDVGestureHandler.m in Sources */, + 3093E2241B16D6A3003F381A /* CDVIntentAndNavigationFilter.m in Sources */, + 7ED95D071AB9028C008C4574 /* CDVHandleOpenURL.m in Sources */, + 4E714D3823F535B500A321AF /* CDVLaunchScreen.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB921F08733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PUBLIC_HEADERS_FOLDER_PATH = include/Cordova; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1DEB922008733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PUBLIC_HEADERS_FOLDER_PATH = include/Cordova; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 1DEB922308733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = CordovaLib_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = Cordova; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 1DEB922408733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = CordovaLib_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = Cordova; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C0C01EB71E3911D50056E6CB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Cordova/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.apache.cordova.Cordova; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C0C01EB81E3911D50056E6CB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Cordova/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.apache.cordova.Cordova; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "CordovaLib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB921F08733DC00010E9CD /* Debug */, + 1DEB922008733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "CordovaLib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB922308733DC00010E9CD /* Debug */, + 1DEB922408733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C0C01EB91E3911D50056E6CB /* Build configuration list for PBXNativeTarget "Cordova" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C0C01EB71E3911D50056E6CB /* Debug */, + C0C01EB81E3911D50056E6CB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/_platforms/ios/CordovaLib/CordovaLib_Prefix.pch b/_platforms/ios/CordovaLib/CordovaLib_Prefix.pch new file mode 100644 index 000000000..954558070 --- /dev/null +++ b/_platforms/ios/CordovaLib/CordovaLib_Prefix.pch @@ -0,0 +1,22 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#ifdef __OBJC__ + #import +#endif diff --git a/_platforms/ios/CordovaLib/VERSION b/_platforms/ios/CordovaLib/VERSION new file mode 100644 index 000000000..6abaeb2f9 --- /dev/null +++ b/_platforms/ios/CordovaLib/VERSION @@ -0,0 +1 @@ +6.2.0 diff --git a/_platforms/ios/CordovaLib/cordova.js b/_platforms/ios/CordovaLib/cordova.js new file mode 100644 index 000000000..37f329f54 --- /dev/null +++ b/_platforms/ios/CordovaLib/cordova.js @@ -0,0 +1,2104 @@ +// Platform: ios +// cordova-js rel/6.0.0-10-g07379820 +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ +;(function() { +var PLATFORM_VERSION_BUILD_LABEL = '6.2.0'; +// file: src/scripts/require.js +var require; +var define; + +(function () { + var modules = {}; + // Stack of moduleIds currently being built. + var requireStack = []; + // Map of module ID -> index into requireStack of modules currently being built. + var inProgressModules = {}; + var SEPARATOR = '.'; + + function build (module) { + var factory = module.factory; + var localRequire = function (id) { + var resultantId = id; + // Its a relative path, so lop off the last portion and add the id (minus "./") + if (id.charAt(0) === '.') { + resultantId = module.id.slice(0, module.id.lastIndexOf(SEPARATOR)) + SEPARATOR + id.slice(2); + } + return require(resultantId); + }; + module.exports = {}; + delete module.factory; + factory(localRequire, module.exports, module); + return module.exports; + } + + require = function (id) { + if (!modules[id]) { + throw new Error('module ' + id + ' not found'); + } else if (id in inProgressModules) { + var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id; + throw new Error('Cycle in require graph: ' + cycle); + } + if (modules[id].factory) { + try { + inProgressModules[id] = requireStack.length; + requireStack.push(id); + return build(modules[id]); + } finally { + delete inProgressModules[id]; + requireStack.pop(); + } + } + return modules[id].exports; + }; + + define = function (id, factory) { + if (Object.prototype.hasOwnProperty.call(modules, id)) { + throw new Error('module ' + id + ' already defined'); + } + + modules[id] = { + id: id, + factory: factory + }; + }; + + define.remove = function (id) { + delete modules[id]; + }; + + define.moduleMap = modules; +})(); + +// Export for use in node +if (typeof module === 'object' && typeof require === 'function') { + module.exports.require = require; + module.exports.define = define; +} + +// file: src/cordova.js +define("cordova", function(require, exports, module) { + +// Workaround for Windows 10 in hosted environment case +// http://www.w3.org/html/wg/drafts/html/master/browsers.html#named-access-on-the-window-object +if (window.cordova && !(window.cordova instanceof HTMLElement)) { + throw new Error('cordova already defined'); +} + +var channel = require('cordova/channel'); +var platform = require('cordova/platform'); + +/** + * Intercept calls to addEventListener + removeEventListener and handle deviceready, + * resume, and pause events. + */ +var m_document_addEventListener = document.addEventListener; +var m_document_removeEventListener = document.removeEventListener; +var m_window_addEventListener = window.addEventListener; +var m_window_removeEventListener = window.removeEventListener; + +/** + * Houses custom event handlers to intercept on document + window event listeners. + */ +var documentEventHandlers = {}; +var windowEventHandlers = {}; + +document.addEventListener = function (evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof documentEventHandlers[e] !== 'undefined') { + documentEventHandlers[e].subscribe(handler); + } else { + m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +window.addEventListener = function (evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof windowEventHandlers[e] !== 'undefined') { + windowEventHandlers[e].subscribe(handler); + } else { + m_window_addEventListener.call(window, evt, handler, capture); + } +}; + +document.removeEventListener = function (evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof documentEventHandlers[e] !== 'undefined') { + documentEventHandlers[e].unsubscribe(handler); + } else { + m_document_removeEventListener.call(document, evt, handler, capture); + } +}; + +window.removeEventListener = function (evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof windowEventHandlers[e] !== 'undefined') { + windowEventHandlers[e].unsubscribe(handler); + } else { + m_window_removeEventListener.call(window, evt, handler, capture); + } +}; + +function createEvent (type, data) { + var event = document.createEvent('Events'); + event.initEvent(type, false, false); + if (data) { + for (var i in data) { + if (Object.prototype.hasOwnProperty.call(data, i)) { + event[i] = data[i]; + } + } + } + return event; +} + +var cordova = { + define: define, + require: require, + version: PLATFORM_VERSION_BUILD_LABEL, + platformVersion: PLATFORM_VERSION_BUILD_LABEL, + platformId: platform.id, + + /** + * Methods to add/remove your own addEventListener hijacking on document + window. + */ + addWindowEventHandler: function (event) { + return (windowEventHandlers[event] = channel.create(event)); + }, + addStickyDocumentEventHandler: function (event) { + return (documentEventHandlers[event] = channel.createSticky(event)); + }, + addDocumentEventHandler: function (event) { + return (documentEventHandlers[event] = channel.create(event)); + }, + removeWindowEventHandler: function (event) { + delete windowEventHandlers[event]; + }, + removeDocumentEventHandler: function (event) { + delete documentEventHandlers[event]; + }, + + /** + * Retrieve original event handlers that were replaced by Cordova + * + * @return object + */ + getOriginalHandlers: function () { + return { + document: { + addEventListener: m_document_addEventListener, + removeEventListener: m_document_removeEventListener + }, + window: { + addEventListener: m_window_addEventListener, + removeEventListener: m_window_removeEventListener + } + }; + }, + + /** + * Method to fire event from native code + * bNoDetach is required for events which cause an exception which needs to be caught in native code + */ + fireDocumentEvent: function (type, data, bNoDetach) { + var evt = createEvent(type, data); + if (typeof documentEventHandlers[type] !== 'undefined') { + if (bNoDetach) { + documentEventHandlers[type].fire(evt); + } else { + setTimeout(function () { + // Fire deviceready on listeners that were registered before cordova.js was loaded. + if (type === 'deviceready') { + document.dispatchEvent(evt); + } + documentEventHandlers[type].fire(evt); + }, 0); + } + } else { + document.dispatchEvent(evt); + } + }, + + fireWindowEvent: function (type, data) { + var evt = createEvent(type, data); + if (typeof windowEventHandlers[type] !== 'undefined') { + setTimeout(function () { + windowEventHandlers[type].fire(evt); + }, 0); + } else { + window.dispatchEvent(evt); + } + }, + + /** + * Plugin callback mechanism. + */ + // Randomize the starting callbackId to avoid collisions after refreshing or navigating. + // This way, it's very unlikely that any new callback would get the same callbackId as an old callback. + callbackId: Math.floor(Math.random() * 2000000000), + callbacks: {}, + callbackStatus: { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }, + + /** + * Called by native code when returning successful result from an action. + */ + callbackSuccess: function (callbackId, args) { + cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback); + }, + + /** + * Called by native code when returning error result from an action. + */ + callbackError: function (callbackId, args) { + // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. + // Derive success from status. + cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback); + }, + + /** + * Called by native code when returning the result from an action. + */ + callbackFromNative: function (callbackId, isSuccess, status, args, keepCallback) { + try { + var callback = cordova.callbacks[callbackId]; + if (callback) { + if (isSuccess && status === cordova.callbackStatus.OK) { + callback.success && callback.success.apply(null, args); + } else if (!isSuccess) { + callback.fail && callback.fail.apply(null, args); + } + /* + else + Note, this case is intentionally not caught. + this can happen if isSuccess is true, but callbackStatus is NO_RESULT + which is used to remove a callback from the list without calling the callbacks + typically keepCallback is false in this case + */ + // Clear callback if not expecting any more results + if (!keepCallback) { + delete cordova.callbacks[callbackId]; + } + } + } catch (err) { + var msg = 'Error in ' + (isSuccess ? 'Success' : 'Error') + ' callbackId: ' + callbackId + ' : ' + err; + cordova.fireWindowEvent('cordovacallbackerror', { message: msg, error: err }); + throw err; + } + }, + + addConstructor: function (func) { + channel.onCordovaReady.subscribe(function () { + try { + func(); + } catch (e) { + console.log('Failed to run constructor: ' + e); + } + }); + } +}; + +module.exports = cordova; + +}); + +// file: src/common/argscheck.js +define("cordova/argscheck", function(require, exports, module) { + +var utils = require('cordova/utils'); + +var moduleExports = module.exports; + +var typeMap = { + A: 'Array', + D: 'Date', + N: 'Number', + S: 'String', + F: 'Function', + O: 'Object' +}; + +function extractParamName (callee, argIndex) { + return (/\(\s*([^)]*?)\s*\)/).exec(callee)[1].split(/\s*,\s*/)[argIndex]; +} + +/** + * Checks the given arguments' types and throws if they are not as expected. + * + * `spec` is a string where each character stands for the required type of the + * argument at the same position. In other words: the character at `spec[i]` + * specifies the required type for `args[i]`. The characters in `spec` are the + * first letter of the required type's name. The supported types are: + * + * Array, Date, Number, String, Function, Object + * + * Lowercase characters specify arguments that must not be `null` or `undefined` + * while uppercase characters allow those values to be passed. + * + * Finally, `*` can be used to allow any type at the corresponding position. + * + * @example + * function foo (arr, opts) { + * // require `arr` to be an Array and `opts` an Object, null or undefined + * checkArgs('aO', 'my.package.foo', arguments); + * // ... + * } + * @param {String} spec - the type specification for `args` as described above + * @param {String} functionName - full name of the callee. + * Used in the error message + * @param {Array|arguments} args - the arguments to be checked against `spec` + * @param {Function} [opt_callee=args.callee] - the recipient of `args`. + * Used to extract parameter names for the error message + * @throws {TypeError} if args do not satisfy spec + */ +function checkArgs (spec, functionName, args, opt_callee) { + if (!moduleExports.enableChecks) { + return; + } + var errMsg = null; + var typeName; + for (var i = 0; i < spec.length; ++i) { + var c = spec.charAt(i); + var cUpper = c.toUpperCase(); + var arg = args[i]; + // Asterix means allow anything. + if (c === '*') { + continue; + } + typeName = utils.typeName(arg); + if ((arg === null || arg === undefined) && c === cUpper) { + continue; + } + if (typeName !== typeMap[cUpper]) { + errMsg = 'Expected ' + typeMap[cUpper]; + break; + } + } + if (errMsg) { + errMsg += ', but got ' + typeName + '.'; + errMsg = 'Wrong type for parameter "' + extractParamName(opt_callee || args.callee, i) + '" of ' + functionName + ': ' + errMsg; + // Don't log when running unit tests. + if (typeof jasmine === 'undefined') { + console.error(errMsg); + } + throw TypeError(errMsg); + } +} + +function getValue (value, defaultValue) { + return value === undefined ? defaultValue : value; +} + +moduleExports.checkArgs = checkArgs; +moduleExports.getValue = getValue; +moduleExports.enableChecks = true; + +}); + +// file: src/common/base64.js +define("cordova/base64", function(require, exports, module) { + +var base64 = exports; + +base64.fromArrayBuffer = function (arrayBuffer) { + var array = new Uint8Array(arrayBuffer); + return uint8ToBase64(array); +}; + +base64.toArrayBuffer = function (str) { + var decodedStr = atob(str); + var arrayBuffer = new ArrayBuffer(decodedStr.length); + var array = new Uint8Array(arrayBuffer); + for (var i = 0, len = decodedStr.length; i < len; i++) { + array[i] = decodedStr.charCodeAt(i); + } + return arrayBuffer; +}; + +// ------------------------------------------------------------------------------ + +/* This code is based on the performance tests at http://jsperf.com/b64tests + * This 12-bit-at-a-time algorithm was the best performing version on all + * platforms tested. + */ + +var b64_6bit = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +var b64_12bit; + +var b64_12bitTable = function () { + b64_12bit = []; + for (var i = 0; i < 64; i++) { + for (var j = 0; j < 64; j++) { + b64_12bit[i * 64 + j] = b64_6bit[i] + b64_6bit[j]; + } + } + b64_12bitTable = function () { return b64_12bit; }; + return b64_12bit; +}; + +function uint8ToBase64 (rawData) { + var numBytes = rawData.byteLength; + var output = ''; + var segment; + var table = b64_12bitTable(); + for (var i = 0; i < numBytes - 2; i += 3) { + segment = (rawData[i] << 16) + (rawData[i + 1] << 8) + rawData[i + 2]; + output += table[segment >> 12]; + output += table[segment & 0xfff]; + } + if (numBytes - i === 2) { + segment = (rawData[i] << 16) + (rawData[i + 1] << 8); + output += table[segment >> 12]; + output += b64_6bit[(segment & 0xfff) >> 6]; + output += '='; + } else if (numBytes - i === 1) { + segment = (rawData[i] << 16); + output += table[segment >> 12]; + output += '=='; + } + return output; +} + +}); + +// file: src/common/builder.js +define("cordova/builder", function(require, exports, module) { + +var utils = require('cordova/utils'); + +function each (objects, func, context) { + for (var prop in objects) { + if (Object.prototype.hasOwnProperty.call(objects, prop)) { + func.apply(context, [objects[prop], prop]); + } + } +} + +function clobber (obj, key, value) { + var needsProperty = false; + try { + obj[key] = value; + } catch (e) { + needsProperty = true; + } + // Getters can only be overridden by getters. + if (needsProperty || obj[key] !== value) { + utils.defineGetter(obj, key, function () { + return value; + }); + } +} + +function assignOrWrapInDeprecateGetter (obj, key, value, message) { + if (message) { + utils.defineGetter(obj, key, function () { + console.log(message); + delete obj[key]; + clobber(obj, key, value); + return value; + }); + } else { + clobber(obj, key, value); + } +} + +function include (parent, objects, clobber, merge) { + each(objects, function (obj, key) { + try { + var result = obj.path ? require(obj.path) : {}; + + if (clobber) { + // Clobber if it doesn't exist. + if (typeof parent[key] === 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else if (typeof obj.path !== 'undefined') { + // If merging, merge properties onto parent, otherwise, clobber. + if (merge) { + recursiveMerge(parent[key], result); + } else { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } + } + result = parent[key]; + } else { + // Overwrite if not currently defined. + if (typeof parent[key] === 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else { + // Set result to what already exists, so we can build children into it if they exist. + result = parent[key]; + } + } + + if (obj.children) { + include(result, obj.children, clobber, merge); + } + } catch (e) { + utils.alert('Exception building Cordova JS globals: ' + e + ' for key "' + key + '"'); + } + }); +} + +/** + * Merge properties from one object onto another recursively. Properties from + * the src object will overwrite existing target property. + * + * @param target Object to merge properties into. + * @param src Object to merge properties from. + */ +function recursiveMerge (target, src) { + for (var prop in src) { + if (Object.prototype.hasOwnProperty.call(src, prop)) { + if (target.prototype && target.prototype.constructor === target) { + // If the target object is a constructor override off prototype. + clobber(target.prototype, prop, src[prop]); + } else { + if (typeof src[prop] === 'object' && typeof target[prop] === 'object') { + recursiveMerge(target[prop], src[prop]); + } else { + clobber(target, prop, src[prop]); + } + } + } + } +} + +exports.buildIntoButDoNotClobber = function (objects, target) { + include(target, objects, false, false); +}; +exports.buildIntoAndClobber = function (objects, target) { + include(target, objects, true, false); +}; +exports.buildIntoAndMerge = function (objects, target) { + include(target, objects, true, true); +}; +exports.recursiveMerge = recursiveMerge; +exports.assignOrWrapInDeprecateGetter = assignOrWrapInDeprecateGetter; + +}); + +// file: src/common/channel.js +define("cordova/channel", function(require, exports, module) { + +var utils = require('cordova/utils'); +var nextGuid = 1; + +/** + * Custom pub-sub "channel" that can have functions subscribed to it + * This object is used to define and control firing of events for + * cordova initialization, as well as for custom events thereafter. + * + * The order of events during page load and Cordova startup is as follows: + * + * onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed. + * onNativeReady* Internal event that indicates the Cordova native side is ready. + * onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created. + * onDeviceReady* User event fired to indicate that Cordova is ready + * onResume User event fired to indicate a start/resume lifecycle event + * onPause User event fired to indicate a pause lifecycle event + * + * The events marked with an * are sticky. Once they have fired, they will stay in the fired state. + * All listeners that subscribe after the event is fired will be executed right away. + * + * The only Cordova events that user code should register for are: + * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript + * pause App has moved to background + * resume App has returned to foreground + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + * document.addEventListener("pause", myPauseListener, false); + * + * The DOM lifecycle events should be used for saving and restoring state + * window.onload + * window.onunload + * + */ + +/** + * Channel + * @constructor + * @param type String the channel name + */ +var Channel = function (type, sticky) { + this.type = type; + // Map of guid -> function. + this.handlers = {}; + // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired. + this.state = sticky ? 1 : 0; + // Used in sticky mode to remember args passed to fire(). + this.fireArgs = null; + // Used by onHasSubscribersChange to know if there are any listeners. + this.numHandlers = 0; + // Function that is called when the first listener is subscribed, or when + // the last listener is unsubscribed. + this.onHasSubscribersChange = null; +}; +var channel = { + /** + * Calls the provided function only after all of the channels specified + * have been fired. All channels must be sticky channels. + */ + join: function (h, c) { + var len = c.length; + var i = len; + var f = function () { + if (!(--i)) h(); + }; + for (var j = 0; j < len; j++) { + if (c[j].state === 0) { + throw Error('Can only use join with sticky channels.'); + } + c[j].subscribe(f); + } + if (!len) h(); + }, + + create: function (type) { + return (channel[type] = new Channel(type, false)); + }, + createSticky: function (type) { + return (channel[type] = new Channel(type, true)); + }, + + /** + * cordova Channels that must fire before "deviceready" is fired. + */ + deviceReadyChannelsArray: [], + deviceReadyChannelsMap: {}, + + /** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up Cordova's "deviceready" event until the feature has been initialized + * and Cordova.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ + waitForInitialization: function (feature) { + if (feature) { + var c = channel[feature] || this.createSticky(feature); + this.deviceReadyChannelsMap[feature] = c; + this.deviceReadyChannelsArray.push(c); + } + }, + + /** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ + initializationComplete: function (feature) { + var c = this.deviceReadyChannelsMap[feature]; + if (c) { + c.fire(); + } + } +}; + +function checkSubscriptionArgument (argument) { + if (typeof argument !== 'function' && typeof argument.handleEvent !== 'function') { + throw new Error( + 'Must provide a function or an EventListener object ' + + 'implementing the handleEvent interface.' + ); + } +} + +/** + * Subscribes the given function to the channel. Any time that + * Channel.fire is called so too will the function. + * Optionally specify an execution context for the function + * and a guid that can be used to stop subscribing to the channel. + * Returns the guid. + */ +Channel.prototype.subscribe = function (eventListenerOrFunction, eventListener) { + checkSubscriptionArgument(eventListenerOrFunction); + var handleEvent, guid; + + if (eventListenerOrFunction && typeof eventListenerOrFunction === 'object') { + // Received an EventListener object implementing the handleEvent interface + handleEvent = eventListenerOrFunction.handleEvent; + eventListener = eventListenerOrFunction; + } else { + // Received a function to handle event + handleEvent = eventListenerOrFunction; + } + + if (this.state === 2) { + handleEvent.apply(eventListener || this, this.fireArgs); + return; + } + + guid = eventListenerOrFunction.observer_guid; + if (typeof eventListener === 'object') { + handleEvent = utils.close(eventListener, handleEvent); + } + + if (!guid) { + // First time any channel has seen this subscriber + guid = '' + nextGuid++; + } + handleEvent.observer_guid = guid; + eventListenerOrFunction.observer_guid = guid; + + // Don't add the same handler more than once. + if (!this.handlers[guid]) { + this.handlers[guid] = handleEvent; + this.numHandlers++; + if (this.numHandlers === 1) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Unsubscribes the function with the given guid from the channel. + */ +Channel.prototype.unsubscribe = function (eventListenerOrFunction) { + checkSubscriptionArgument(eventListenerOrFunction); + var handleEvent, guid, handler; + + if (eventListenerOrFunction && typeof eventListenerOrFunction === 'object') { + // Received an EventListener object implementing the handleEvent interface + handleEvent = eventListenerOrFunction.handleEvent; + } else { + // Received a function to handle event + handleEvent = eventListenerOrFunction; + } + + guid = handleEvent.observer_guid; + handler = this.handlers[guid]; + if (handler) { + delete this.handlers[guid]; + this.numHandlers--; + if (this.numHandlers === 0) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Calls all functions subscribed to this channel. + */ +Channel.prototype.fire = function (e) { + var fireArgs = Array.prototype.slice.call(arguments); + // Apply stickiness. + if (this.state === 1) { + this.state = 2; + this.fireArgs = fireArgs; + } + if (this.numHandlers) { + // Copy the values first so that it is safe to modify it from within + // callbacks. + var toCall = []; + for (var item in this.handlers) { + toCall.push(this.handlers[item]); + } + for (var i = 0; i < toCall.length; ++i) { + toCall[i].apply(this, fireArgs); + } + if (this.state === 2 && this.numHandlers) { + this.numHandlers = 0; + this.handlers = {}; + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +// defining them here so they are ready super fast! +// DOM event that is received when the web page is loaded and parsed. +channel.createSticky('onDOMContentLoaded'); + +// Event to indicate the Cordova native side is ready. +channel.createSticky('onNativeReady'); + +// Event to indicate that all Cordova JavaScript objects have been created +// and it's time to run plugin constructors. +channel.createSticky('onCordovaReady'); + +// Event to indicate that all automatically loaded JS plugins are loaded and ready. +// FIXME remove this +channel.createSticky('onPluginsReady'); + +// Event to indicate that Cordova is ready +channel.createSticky('onDeviceReady'); + +// Event to indicate a resume lifecycle event +channel.create('onResume'); + +// Event to indicate a pause lifecycle event +channel.create('onPause'); + +// Channels that must fire before "deviceready" is fired. +channel.waitForInitialization('onCordovaReady'); +channel.waitForInitialization('onDOMContentLoaded'); + +module.exports = channel; + +}); + +// file: ../cordova-ios/cordova-js-src/exec.js +define("cordova/exec", function(require, exports, module) { + +/** + * Creates the exec bridge used to notify the native code of + * commands. + */ +var cordova = require('cordova'); +var utils = require('cordova/utils'); +var base64 = require('cordova/base64'); + +function massageArgsJsToNative (args) { + if (!args || utils.typeName(args) !== 'Array') { + return args; + } + var ret = []; + args.forEach(function (arg, i) { + if (utils.typeName(arg) === 'ArrayBuffer') { + ret.push({ + CDVType: 'ArrayBuffer', + data: base64.fromArrayBuffer(arg) + }); + } else { + ret.push(arg); + } + }); + return ret; +} + +function massageMessageNativeToJs (message) { + if (message.CDVType === 'ArrayBuffer') { + var stringToArrayBuffer = function (str) { + var ret = new Uint8Array(str.length); + for (var i = 0; i < str.length; i++) { + ret[i] = str.charCodeAt(i); + } + return ret.buffer; + }; + var base64ToArrayBuffer = function (b64) { + return stringToArrayBuffer(atob(b64)); // eslint-disable-line no-undef + }; + message = base64ToArrayBuffer(message.data); + } + return message; +} + +function convertMessageToArgsNativeToJs (message) { + var args = []; + if (!message || !Object.prototype.hasOwnProperty.call(message, 'CDVType')) { + args.push(message); + } else if (message.CDVType === 'MultiPart') { + message.messages.forEach(function (e) { + args.push(massageMessageNativeToJs(e)); + }); + } else { + args.push(massageMessageNativeToJs(message)); + } + return args; +} + +var iOSExec = function () { + var successCallback, failCallback, service, action, actionArgs; + var callbackId = null; + if (typeof arguments[0] !== 'string') { + // FORMAT ONE + successCallback = arguments[0]; + failCallback = arguments[1]; + service = arguments[2]; + action = arguments[3]; + actionArgs = arguments[4]; + + // Since we need to maintain backwards compatibility, we have to pass + // an invalid callbackId even if no callback was provided since plugins + // will be expecting it. The Cordova.exec() implementation allocates + // an invalid callbackId and passes it even if no callbacks were given. + callbackId = 'INVALID'; + } else { + throw new Error('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' + // eslint-disable-line + 'cordova.exec(null, null, \'Service\', \'action\', [ arg1, arg2 ]);'); + } + + // If actionArgs is not provided, default to an empty array + actionArgs = actionArgs || []; + + // Register the callbacks and add the callbackId to the positional + // arguments if given. + if (successCallback || failCallback) { + callbackId = service + cordova.callbackId++; + cordova.callbacks[callbackId] = + { success: successCallback, fail: failCallback }; + } + + actionArgs = massageArgsJsToNative(actionArgs); + + // CB-10133 DataClone DOM Exception 25 guard (fast function remover) + var command = [callbackId, service, action, JSON.parse(JSON.stringify(actionArgs))]; + window.webkit.messageHandlers.cordova.postMessage(command); +}; + +iOSExec.nativeCallback = function (callbackId, status, message, keepCallback, debug) { + var success = status === 0 || status === 1; + var args = convertMessageToArgsNativeToJs(message); + Promise.resolve().then(function () { + cordova.callbackFromNative(callbackId, success, status, args, keepCallback); // eslint-disable-line + }); +}; + +// for backwards compatibility +iOSExec.nativeEvalAndFetch = function (func) { + try { + func(); + } catch (e) { + console.log(e); + } +}; + +// Proxy the exec for bridge changes. See CB-10106 + +function cordovaExec () { + var cexec = require('cordova/exec'); + var cexec_valid = (typeof cexec.nativeFetchMessages === 'function') && (typeof cexec.nativeEvalAndFetch === 'function') && (typeof cexec.nativeCallback === 'function'); + return (cexec_valid && execProxy !== cexec) ? cexec : iOSExec; +} + +function execProxy () { + cordovaExec().apply(null, arguments); +} + +execProxy.nativeFetchMessages = function () { + return cordovaExec().nativeFetchMessages.apply(null, arguments); +}; + +execProxy.nativeEvalAndFetch = function () { + return cordovaExec().nativeEvalAndFetch.apply(null, arguments); +}; + +execProxy.nativeCallback = function () { + return cordovaExec().nativeCallback.apply(null, arguments); +}; + +module.exports = execProxy; + +}); + +// file: src/common/exec/proxy.js +define("cordova/exec/proxy", function(require, exports, module) { + +// internal map of proxy function +var CommandProxyMap = {}; + +module.exports = { + + // example: cordova.commandProxy.add("Accelerometer",{getCurrentAcceleration: function(successCallback, errorCallback, options) {...},...); + add: function (id, proxyObj) { + console.log('adding proxy for ' + id); + CommandProxyMap[id] = proxyObj; + return proxyObj; + }, + + // cordova.commandProxy.remove("Accelerometer"); + remove: function (id) { + var proxy = CommandProxyMap[id]; + delete CommandProxyMap[id]; + CommandProxyMap[id] = null; + return proxy; + }, + + get: function (service, action) { + return (CommandProxyMap[service] ? CommandProxyMap[service][action] : null); + } +}; + +}); + +// file: src/common/init.js +define("cordova/init", function(require, exports, module) { + +var channel = require('cordova/channel'); +var cordova = require('cordova'); +var modulemapper = require('cordova/modulemapper'); +var platform = require('cordova/platform'); +var pluginloader = require('cordova/pluginloader'); + +var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady]; + +function logUnfiredChannels (arr) { + for (var i = 0; i < arr.length; ++i) { + if (arr[i].state !== 2) { + console.log('Channel not fired: ' + arr[i].type); + } + } +} + +window.setTimeout(function () { + if (channel.onDeviceReady.state !== 2) { + console.log('deviceready has not fired after 5 seconds.'); + logUnfiredChannels(platformInitChannelsArray); + logUnfiredChannels(channel.deviceReadyChannelsArray); + } +}, 5000); + +if (!window.console) { + window.console = { + log: function () {} + }; +} +if (!window.console.warn) { + window.console.warn = function (msg) { + this.log('warn: ' + msg); + }; +} + +// Register pause, resume and deviceready channels as events on document. +channel.onPause = cordova.addDocumentEventHandler('pause'); +channel.onResume = cordova.addDocumentEventHandler('resume'); +channel.onActivated = cordova.addDocumentEventHandler('activated'); +channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready'); + +// Listen for DOMContentLoaded and notify our channel subscribers. +if (document.readyState === 'complete' || document.readyState === 'interactive') { + channel.onDOMContentLoaded.fire(); +} else { + document.addEventListener('DOMContentLoaded', function () { + channel.onDOMContentLoaded.fire(); + }, false); +} + +// _nativeReady is global variable that the native side can set +// to signify that the native code is ready. It is a global since +// it may be called before any cordova JS is ready. +if (window._nativeReady) { + channel.onNativeReady.fire(); +} + +modulemapper.clobbers('cordova', 'cordova'); +modulemapper.clobbers('cordova/exec', 'cordova.exec'); +modulemapper.clobbers('cordova/exec', 'Cordova.exec'); + +// Call the platform-specific initialization. +platform.bootstrap && platform.bootstrap(); + +// Wrap in a setTimeout to support the use-case of having plugin JS appended to cordova.js. +// The delay allows the attached modules to be defined before the plugin loader looks for them. +setTimeout(function () { + pluginloader.load(function () { + channel.onPluginsReady.fire(); + }); +}, 0); + +/** + * Create all cordova objects once native side is ready. + */ +channel.join(function () { + modulemapper.mapModules(window); + + platform.initialize && platform.initialize(); + + // Fire event to notify that all objects are created + channel.onCordovaReady.fire(); + + // Fire onDeviceReady event once page has fully loaded, all + // constructors have run and cordova info has been received from native + // side. + channel.join(function () { + require('cordova').fireDocumentEvent('deviceready'); + }, channel.deviceReadyChannelsArray); +}, platformInitChannelsArray); + +}); + +// file: src/common/modulemapper.js +define("cordova/modulemapper", function(require, exports, module) { + +var builder = require('cordova/builder'); +var moduleMap = define.moduleMap; +var symbolList; +var deprecationMap; + +exports.reset = function () { + symbolList = []; + deprecationMap = {}; +}; + +function addEntry (strategy, moduleName, symbolPath, opt_deprecationMessage) { + if (!(moduleName in moduleMap)) { + throw new Error('Module ' + moduleName + ' does not exist.'); + } + symbolList.push(strategy, moduleName, symbolPath); + if (opt_deprecationMessage) { + deprecationMap[symbolPath] = opt_deprecationMessage; + } +} + +// Note: Android 2.3 does have Function.bind(). +exports.clobbers = function (moduleName, symbolPath, opt_deprecationMessage) { + addEntry('c', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.merges = function (moduleName, symbolPath, opt_deprecationMessage) { + addEntry('m', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.defaults = function (moduleName, symbolPath, opt_deprecationMessage) { + addEntry('d', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.runs = function (moduleName) { + addEntry('r', moduleName, null); +}; + +function prepareNamespace (symbolPath, context) { + if (!symbolPath) { + return context; + } + return symbolPath.split('.').reduce(function (cur, part) { + return (cur[part] = cur[part] || {}); + }, context); +} + +exports.mapModules = function (context) { + var origSymbols = {}; + context.CDV_origSymbols = origSymbols; + for (var i = 0, len = symbolList.length; i < len; i += 3) { + var strategy = symbolList[i]; + var moduleName = symbolList[i + 1]; + var module = require(moduleName); + // + if (strategy === 'r') { + continue; + } + var symbolPath = symbolList[i + 2]; + var lastDot = symbolPath.lastIndexOf('.'); + var namespace = symbolPath.substr(0, lastDot); + var lastName = symbolPath.substr(lastDot + 1); + + var deprecationMsg = symbolPath in deprecationMap ? 'Access made to deprecated symbol: ' + symbolPath + '. ' + deprecationMsg : null; + var parentObj = prepareNamespace(namespace, context); + var target = parentObj[lastName]; + + if (strategy === 'm' && target) { + builder.recursiveMerge(target, module); + } else if ((strategy === 'd' && !target) || (strategy !== 'd')) { + if (!(symbolPath in origSymbols)) { + origSymbols[symbolPath] = target; + } + builder.assignOrWrapInDeprecateGetter(parentObj, lastName, module, deprecationMsg); + } + } +}; + +exports.getOriginalSymbol = function (context, symbolPath) { + var origSymbols = context.CDV_origSymbols; + if (origSymbols && (symbolPath in origSymbols)) { + return origSymbols[symbolPath]; + } + var parts = symbolPath.split('.'); + var obj = context; + for (var i = 0; i < parts.length; ++i) { + obj = obj && obj[parts[i]]; + } + return obj; +}; + +exports.reset(); + +}); + +// file: ../cordova-ios/cordova-js-src/platform.js +define("cordova/platform", function(require, exports, module) { + +module.exports = { + id: 'ios', + bootstrap: function () { + // Attach the console polyfill that is iOS-only to window.console + // see the file under plugin/ios/console.js + require('cordova/modulemapper').clobbers('cordova/plugin/ios/console', 'window.console'); + + // Attach the wkwebkit utility to window.WkWebView + // see the file under plugin/ios/wkwebkit.js + require('cordova/modulemapper').clobbers('cordova/plugin/ios/wkwebkit', 'window.WkWebView'); + + // Attach the splashscreen utility to window.navigator.splashscreen + // see the file under plugin/ios/launchscreen.js + require('cordova/modulemapper').clobbers('cordova/plugin/ios/launchscreen', 'navigator.splashscreen'); + + require('cordova/channel').onNativeReady.fire(); + } +}; + +}); + +// file: ../cordova-ios/cordova-js-src/plugin/ios/console.js +define("cordova/plugin/ios/console", function(require, exports, module) { + +// ------------------------------------------------------------------------------ + +var logger = require('cordova/plugin/ios/logger'); + +// ------------------------------------------------------------------------------ +// object that we're exporting +// ------------------------------------------------------------------------------ +var console = module.exports; + +// ------------------------------------------------------------------------------ +// copy of the original console object +// ------------------------------------------------------------------------------ +var WinConsole = window.console; + +// ------------------------------------------------------------------------------ +// whether to use the logger +// ------------------------------------------------------------------------------ +var UseLogger = false; + +// ------------------------------------------------------------------------------ +// Timers +// ------------------------------------------------------------------------------ +var Timers = {}; + +// ------------------------------------------------------------------------------ +// used for unimplemented methods +// ------------------------------------------------------------------------------ +function noop () {} + +// ------------------------------------------------------------------------------ +// used for unimplemented methods +// ------------------------------------------------------------------------------ +console.useLogger = function (value) { + if (arguments.length) UseLogger = !!value; + + if (UseLogger) { + if (logger.useConsole()) { + throw new Error('console and logger are too intertwingly'); + } + } + + return UseLogger; +}; + +// ------------------------------------------------------------------------------ +console.log = function () { + if (logger.useConsole()) return; + logger.log.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.error = function () { + if (logger.useConsole()) return; + logger.error.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.warn = function () { + if (logger.useConsole()) return; + logger.warn.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.info = function () { + if (logger.useConsole()) return; + logger.info.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.debug = function () { + if (logger.useConsole()) return; + logger.debug.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.assert = function (expression) { + if (expression) return; + + var message = logger.format.apply(logger.format, [].slice.call(arguments, 1)); + console.log('ASSERT: ' + message); +}; + +// ------------------------------------------------------------------------------ +console.clear = function () {}; + +// ------------------------------------------------------------------------------ +console.dir = function (object) { + console.log('%o', object); +}; + +// ------------------------------------------------------------------------------ +console.dirxml = function (node) { + console.log(node.innerHTML); +}; + +// ------------------------------------------------------------------------------ +console.trace = noop; + +// ------------------------------------------------------------------------------ +console.group = console.log; + +// ------------------------------------------------------------------------------ +console.groupCollapsed = console.log; + +// ------------------------------------------------------------------------------ +console.groupEnd = noop; + +// ------------------------------------------------------------------------------ +console.time = function (name) { + Timers[name] = new Date().valueOf(); +}; + +// ------------------------------------------------------------------------------ +console.timeEnd = function (name) { + var timeStart = Timers[name]; + if (!timeStart) { + console.warn('unknown timer: ' + name); + return; + } + + var timeElapsed = new Date().valueOf() - timeStart; + console.log(name + ': ' + timeElapsed + 'ms'); +}; + +// ------------------------------------------------------------------------------ +console.timeStamp = noop; + +// ------------------------------------------------------------------------------ +console.profile = noop; + +// ------------------------------------------------------------------------------ +console.profileEnd = noop; + +// ------------------------------------------------------------------------------ +console.count = noop; + +// ------------------------------------------------------------------------------ +console.exception = console.log; + +// ------------------------------------------------------------------------------ +console.table = function (data, columns) { + console.log('%o', data); +}; + +// ------------------------------------------------------------------------------ +// return a new function that calls both functions passed as args +// ------------------------------------------------------------------------------ +function wrappedOrigCall (orgFunc, newFunc) { + return function () { + var args = [].slice.call(arguments); + try { orgFunc.apply(WinConsole, args); } catch (e) {} + try { newFunc.apply(console, args); } catch (e) {} + }; +} + +// ------------------------------------------------------------------------------ +// For every function that exists in the original console object, that +// also exists in the new console object, wrap the new console method +// with one that calls both +// ------------------------------------------------------------------------------ +for (var key in console) { + if (typeof WinConsole[key] === 'function') { + console[key] = wrappedOrigCall(WinConsole[key], console[key]); + } +} + +}); + +// file: ../cordova-ios/cordova-js-src/plugin/ios/launchscreen.js +define("cordova/plugin/ios/launchscreen", function(require, exports, module) { + +var exec = require('cordova/exec'); + +var launchscreen = { + show: function () { + exec(null, null, 'LaunchScreen', 'show', []); + }, + hide: function () { + exec(null, null, 'LaunchScreen', 'hide', []); + } +}; + +module.exports = launchscreen; + +}); + +// file: ../cordova-ios/cordova-js-src/plugin/ios/logger.js +define("cordova/plugin/ios/logger", function(require, exports, module) { + +// ------------------------------------------------------------------------------ +// The logger module exports the following properties/functions: +// +// LOG - constant for the level LOG +// ERROR - constant for the level ERROR +// WARN - constant for the level WARN +// INFO - constant for the level INFO +// DEBUG - constant for the level DEBUG +// logLevel() - returns current log level +// logLevel(value) - sets and returns a new log level +// useConsole() - returns whether logger is using console +// useConsole(value) - sets and returns whether logger is using console +// log(message,...) - logs a message at level LOG +// error(message,...) - logs a message at level ERROR +// warn(message,...) - logs a message at level WARN +// info(message,...) - logs a message at level INFO +// debug(message,...) - logs a message at level DEBUG +// logLevel(level,message,...) - logs a message specified level +// +// ------------------------------------------------------------------------------ + +var logger = exports; + +var exec = require('cordova/exec'); + +var UseConsole = false; +var UseLogger = true; +var Queued = []; +var DeviceReady = false; +var CurrentLevel; + +var originalConsole = console; + +/** + * Logging levels + */ + +var Levels = [ + 'LOG', + 'ERROR', + 'WARN', + 'INFO', + 'DEBUG' +]; + +/* + * add the logging levels to the logger object and + * to a separate levelsMap object for testing + */ + +var LevelsMap = {}; +for (var i = 0; i < Levels.length; i++) { + var level = Levels[i]; + LevelsMap[level] = i; + logger[level] = level; +} + +CurrentLevel = LevelsMap.WARN; + +/** + * Getter/Setter for the logging level + * + * Returns the current logging level. + * + * When a value is passed, sets the logging level to that value. + * The values should be one of the following constants: + * logger.LOG + * logger.ERROR + * logger.WARN + * logger.INFO + * logger.DEBUG + * + * The value used determines which messages get printed. The logging + * values above are in order, and only messages logged at the logging + * level or above will actually be displayed to the user. E.g., the + * default level is WARN, so only messages logged with LOG, ERROR, or + * WARN will be displayed; INFO and DEBUG messages will be ignored. + */ +logger.level = function (value) { + if (arguments.length) { + if (LevelsMap[value] === null) { + throw new Error('invalid logging level: ' + value); + } + CurrentLevel = LevelsMap[value]; + } + + return Levels[CurrentLevel]; +}; + +/** + * Getter/Setter for the useConsole functionality + * + * When useConsole is true, the logger will log via the + * browser 'console' object. + */ +logger.useConsole = function (value) { + if (arguments.length) UseConsole = !!value; + + if (UseConsole) { + if (typeof console === 'undefined') { + throw new Error('global console object is not defined'); + } + + if (typeof console.log !== 'function') { + throw new Error('global console object does not have a log function'); + } + + if (typeof console.useLogger === 'function') { + if (console.useLogger()) { + throw new Error('console and logger are too intertwingly'); + } + } + } + + return UseConsole; +}; + +/** + * Getter/Setter for the useLogger functionality + * + * When useLogger is true, the logger will log via the + * native Logger plugin. + */ +logger.useLogger = function (value) { + // Enforce boolean + if (arguments.length) UseLogger = !!value; + return UseLogger; +}; + +/** + * Logs a message at the LOG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.log = function (message) { logWithArgs('LOG', arguments); }; + +/** + * Logs a message at the ERROR level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.error = function (message) { logWithArgs('ERROR', arguments); }; + +/** + * Logs a message at the WARN level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.warn = function (message) { logWithArgs('WARN', arguments); }; + +/** + * Logs a message at the INFO level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.info = function (message) { logWithArgs('INFO', arguments); }; + +/** + * Logs a message at the DEBUG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.debug = function (message) { logWithArgs('DEBUG', arguments); }; + +// log at the specified level with args +function logWithArgs (level, args) { + args = [level].concat([].slice.call(args)); + logger.logLevel.apply(logger, args); +} + +// return the correct formatString for an object +function formatStringForMessage (message) { + return (typeof message === 'string') ? '' : '%o'; +} + +/** + * Logs a message at the specified level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.logLevel = function (level /* , ... */) { + // format the message with the parameters + var formatArgs = [].slice.call(arguments, 1); + var fmtString = formatStringForMessage(formatArgs[0]); + if (fmtString.length > 0) { + formatArgs.unshift(fmtString); // add formatString + } + + var message = logger.format.apply(logger.format, formatArgs); + + if (LevelsMap[level] === null) { + throw new Error('invalid logging level: ' + level); + } + + if (LevelsMap[level] > CurrentLevel) return; + + // queue the message if not yet at deviceready + if (!DeviceReady && !UseConsole) { + Queued.push([level, message]); + return; + } + + // Log using the native logger if that is enabled + if (UseLogger) { + exec(null, null, 'Console', 'logLevel', [level, message]); + } + + // Log using the console if that is enabled + if (UseConsole) { + // make sure console is not using logger + if (console.useLogger()) { + throw new Error('console and logger are too intertwingly'); + } + + // log to the console + switch (level) { + case logger.LOG: originalConsole.log(message); break; + case logger.ERROR: originalConsole.log('ERROR: ' + message); break; + case logger.WARN: originalConsole.log('WARN: ' + message); break; + case logger.INFO: originalConsole.log('INFO: ' + message); break; + case logger.DEBUG: originalConsole.log('DEBUG: ' + message); break; + } + } +}; + +/** + * Formats a string and arguments following it ala console.log() + * + * Any remaining arguments will be appended to the formatted string. + * + * for rationale, see FireBug's Console API: + * http://getfirebug.com/wiki/index.php/Console_API + */ +logger.format = function (formatString, args) { + return __format(arguments[0], [].slice.call(arguments, 1)).join(' '); +}; + +// ------------------------------------------------------------------------------ +/** + * Formats a string and arguments following it ala vsprintf() + * + * format chars: + * %j - format arg as JSON + * %o - format arg as JSON + * %c - format arg as '' + * %% - replace with '%' + * any other char following % will format it's + * arg via toString(). + * + * Returns an array containing the formatted string and any remaining + * arguments. + */ +function __format (formatString, args) { + if (formatString === null || formatString === undefined) return ['']; + if (arguments.length === 1) return [formatString.toString()]; + + if (typeof formatString !== 'string') { formatString = formatString.toString(); } + + var pattern = /(.*?)%(.)(.*)/; + var rest = formatString; + var result = []; + + while (args.length) { + var match = pattern.exec(rest); + if (!match) break; + + var arg = args.shift(); + rest = match[3]; + result.push(match[1]); + + if (match[2] === '%') { + result.push('%'); + args.unshift(arg); + continue; + } + + result.push(__formatted(arg, match[2])); + } + + result.push(rest); + + var remainingArgs = [].slice.call(args); + remainingArgs.unshift(result.join('')); + return remainingArgs; +} + +function __formatted (object, formatChar) { + try { + switch (formatChar) { + case 'j': + case 'o': return JSON.stringify(object); + case 'c': return ''; + } + } catch (e) { + return 'error JSON.stringify()ing argument: ' + e; + } + + if ((object === null) || (object === undefined)) { + return Object.prototype.toString.call(object); + } + + return object.toString(); +} + +// ------------------------------------------------------------------------------ +// when deviceready fires, log queued messages +logger.__onDeviceReady = function () { + if (DeviceReady) return; + + DeviceReady = true; + + for (var i = 0; i < Queued.length; i++) { + var messageArgs = Queued[i]; + logger.logLevel(messageArgs[0], messageArgs[1]); + } + + Queued = null; +}; + +// add a deviceready event to log queued messages +document.addEventListener('deviceready', logger.__onDeviceReady, false); + +}); + +// file: ../cordova-ios/cordova-js-src/plugin/ios/wkwebkit.js +define("cordova/plugin/ios/wkwebkit", function(require, exports, module) { + +var exec = require('cordova/exec'); + +var WkWebKit = { + allowsBackForwardNavigationGestures: function (allow) { + exec(null, null, 'CDVWebViewEngine', 'allowsBackForwardNavigationGestures', [allow]); + }, + convertFilePath: function (path) { + if (!path || !window.CDV_ASSETS_URL) { + return path; + } + if (path.startsWith('/')) { + return window.CDV_ASSETS_URL + '/_app_file_' + path; + } + if (path.startsWith('file://')) { + return window.CDV_ASSETS_URL + path.replace('file://', '/_app_file_'); + } + return path; + } +}; + +module.exports = WkWebKit; + +}); + +// file: src/common/pluginloader.js +define("cordova/pluginloader", function(require, exports, module) { + +var modulemapper = require('cordova/modulemapper'); + +// Helper function to inject a + + + + + + */ + mfpjsonstoreready(); + } else { + //console.log('Inject MFP JSONStore Scripts dynamically'); + loadJSONStoreScript(); + } + + function mfpjsonstoreready(){ + var wlevent; + + //console.log("bootstrap.js dispatching mfpjsonjsloaded event"); + + try { + wlevent = new Event('mfpjsonjsloaded'); + } catch (err) { + if (err instanceof TypeError) { + // Trying to use old events + wlevent = document.createEvent('Event'); + wlevent.initEvent('mfpjsonjsloaded', true, true); + } else + console.error(err.message); + } + + // Dispatch the event. + document.dispatchEvent(wlevent); + } + + function loadJSONStoreScript(){ + //console.log("injecting script jsonstore.js"); + injectScript(findCordovaPath() + JSONSTORE_PATH, mfpjsonstoreready, + bootError); + } + + function injectScript(url, onload, onerror) { + var script = document.createElement("script"); + // onload fires even when script fails loads with an error. + script.onload = onload; + // onerror fires for malformed URLs. + script.onerror = onerror; + script.src = url; + document.head.appendChild(script); + } + + function bootError(errMsg) { + throw errMsg; + } +} + +function findCordovaPath() { + var path = null; + var scripts = document.getElementsByTagName('script'); + var startterm = '/cordova.'; + var term = '/cordova.js'; + for (var n = scripts.length-1; n>-1; n--) { + var src = scripts[n].src.replace(/\?.*$/, ''); // Strip any query param (CB-6007). + // APAR 119091: findCordovaPath function to work with hashed builds. + var idx = src.indexOf(startterm); + if (idx >= 0 && src.substring(idx).replace(/cordova\.[^\.\/]*\.js/, "cordova.js") == term) { + term = src.substring(idx); + } + if (src.indexOf(term) === (src.length - term.length)) { + path = src.substring(0, src.length - term.length) + '/'; + break; + } + } + return path; +} + +}); diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp-jsonstore/worklight/jsonstore.js b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp-jsonstore/worklight/jsonstore.js new file mode 100644 index 000000000..162ca821b --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp-jsonstore/worklight/jsonstore.js @@ -0,0 +1,6084 @@ + + +/** + * ================================================================= + * Source file taken from :: jsonstore_stub.js + * ================================================================= + */ + +/** + * @license + Licensed Materials - Property of IBM + + (C) Copyright 2015 IBM Corp. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + /*globals WL_*/ + +var WL = WL || {}; + +WL.JSONStore = (function(_) { + + /*jshint strict:false*/ + + var publicAPI = [ + 'init', + 'get', + 'initCollection', + 'usePassword', + 'clearPassword', + 'closeAll', + 'documentify', + 'changePassword', + 'destroy', + 'getErrorMessage', + 'startTransaction', + 'commitTransaction', + 'rollbackTransaction', + 'fileInfo', + 'QueryPart' + ]; + + var stub = {}; + + var jsonStoreEnabled = function() { + return !(_.isUndefined(WL._JSONStoreImpl)); + }; + + _.each(publicAPI, function(apiName) { + var implName = apiName; + stub[apiName] = + (function(apiName, implName) { + return function() { + if (jsonStoreEnabled()) { + return WL._JSONStoreImpl[implName].apply(WL._JSONStoreImpl, arguments); + } else { + var featureName = 'JSONStore'; + var cmd = 'WL.JSONStore.' + apiName; + var errMsg = 'Failed to call {1} because {0} is missing in the application. Add {0} to the application descriptor, rebuild and deploy it.'; + throw new Error(WL.Utils.formatString(errMsg, featureName, cmd)); + } + }; + })(apiName, implName); + }); + stub['syncOptions'] = { SYNC_NONE : 2, SYNC_UPSTREAM : 1, SYNC_DOWNSTREAM : 0 }; + return stub; +}(WL_)); + + +/** + * ================================================================= + * Source file taken from :: jsonstore.js + * ================================================================= + */ + +/** + * @license + Licensed Materials - Property of IBM + + (C) Copyright 2015 IBM Corp. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/*globals WLJQ, WL_, WLResourceRequest, cordova, sjcl, setTimeout, document, localStorage, JSONStoreWin8, ArrayBuffer */ +/*jshint camelcase:false, maxparams:4*/ + +var WL = WL || {}; + +/** + * JSONStore Modules + */ + +/** +Provides a namespace function for WL +@private +**/ + +WL.namespace = function(ns_string) { + + 'use strict'; + + var parts = ns_string.split('.'), + parent = WL, + i; + + if (parts[0] === 'WL') { + parts = parts.slice(1); + } + + for (i = 0; i < parts.length; i += 1) { + if (typeof parent[parts[i]] === 'undefined') { + parent[parts[i]] = {}; + } + parent = parent[parts[i]]; + } + + return parent; +}; + +/** +Provides constants +@private +**/ +WL.constant = (function() { + + 'use strict'; + + /** + Error Codes - Make sure you update wl.geterrormessage.tests.js + in QA/jsonstore if you change the error codes. + @private + */ + var ERROR = [], + UNKNOWN_FAILURE = -100, + OS_SECURITY_FAILURE = -75, + PERSISTENT_STORE_NOT_OPEN = -50, + FIPS_ENABLEMENT_FAILURE = -40, + TRANSACTION_IN_PROGRESS = -41, + NO_TRANSACTION_IN_PROGRESS = -42, + TRANSACTION_FAILURE = -43, + TRANSACTION_FAILURE_DURING_INIT = -44, + TRANSACTION_FAILURE_DURING_CLOSE_ALL = -45, + TRANSACTION_FAILURE_DURING_DESTROY = -46, + TRANSACTION_FAILURE_DURING_REMOVE_COLLECTION = -47, + TRANSACTION_FAILURE_DURING_ROLLBACK = -48, + + DESTROY_FAILED_FILE_ERROR = -18, + DESTROY_FAILED_METADATA_REMOVAL_FAILURE = -19, + + JSON_STORE_INVALID_JSON_STRUCTURE = -20, + JSON_STORE_STORE_DATA_PROTECTION_KEY_FAILURE = -21, + JSON_STORE_REMOVE_WITH_QUERIES_FAILURE = -22, + JSON_STORE_REPLACE_DOCUMENTS_FAILURE = -23, + JSON_STORE_FILE_INFO_ERROR = -24, + INVALID_SEARCH_FIELD_TYPES = -12, + OPERATION_FAILED_ON_SPECIFIC_DOCUMENT = -11, + ACCEPT_CONDITION_FAILED = -10, + OFFSET_WITHOUT_LIMIT = -9, + INVALID_LIMIT_OR_OFFSET = -8, + INVALID_USERNAME = -7, + USERNAME_MISMATCH_DETECTED = -6, + DESTROY_REMOVE_PERSISTENT_STORE_FAILED = -5, + DESTROY_REMOVE_KEYS_FAILED = -4, + INVALID_KEY_ON_PROVISION = -3, + PROVISION_TABLE_SEARCH_FIELDS_MISMATCH = -2, + PERSISTENT_STORE_FAILURE = -1, + SUCCESS = 0, + BAD_PARAMETER_EXPECTED_INT = 1, + BAD_PARAMETER_EXPECTED_STRING = 2, + BAD_PARAMETER_EXPECTED_FUNCTION = 3, + BAD_PARAMETER_EXPECTED_ALPHANUMERIC_STRING = 4, + BAD_PARAMETER_EXPECTED_OBJECT = 5, + BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT = 6, + BAD_PARAMETER_EXPECTED_DOCUMENT = 7, + FAILED_TO_GET_UNPUSHED_DOCUMENTS_FROM_DB = 8, + NO_ADAPTER_LINKED_TO_COLLECTION = 9, + BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ARRAY_OF_DOCUMENTS = 10, + INVALID_PASSWORD_EXPECTED_ALPHANUMERIC_STRING_WITH_LENGTH_GREATER_THAN_ZERO = 11, + ADAPTER_FAILURE = 12, + BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ID = 13, + CAN_NOT_REPLACE_DEFAULT_FUNCTIONS = 14, + COULD_NOT_MARK_DOCUMENT_PUSHED = 15, + COULD_NOT_GET_SECURE_KEY = 16, + FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER = 17, + FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER_INVALID_LOAD_OBJ = 18, + INVALID_KEY_IN_LOAD_OBJECT = 19, + UNDEFINED_PUSH_OPERATION = 20, + INVALID_ADD_INDEX_KEY = 21, + INVALID_SEARCH_FIELD = 22, + ERROR_CLOSING_ALL = 23, + ERROR_CHANGING_PASSWORD = 24, + ERROR_DURING_DESTROY = 25, + ERROR_CLEARING_COLLECTION = 26, + INVALID_PARAMETER_FOR_FIND_BY_ID = 27, + INVALID_SORT_OBJECT = 28, + INVALID_FILTER_ARRAY = 29, + BAD_PARAMETER_EXPECTED_ARRAY_OF_OBJECTS = 30, + BAD_PARAMETER_EXPECTED_ARRAY_OF_CLEAN_DOCUMENTS = 31, + BAD_PARAMETER_WRONG_SEARCH_CRITERIA = 32; + + ERROR[UNKNOWN_FAILURE] = 'UNKNOWN_FAILURE'; + ERROR[OS_SECURITY_FAILURE] = 'OS_SECURITY_FAILURE'; + ERROR[PERSISTENT_STORE_NOT_OPEN] = 'PERSISTENT_STORE_NOT_OPEN'; + ERROR[FIPS_ENABLEMENT_FAILURE] = 'FIPS_ENABLEMENT_FAILURE'; + ERROR[TRANSACTION_IN_PROGRESS] = 'TRANSACTION_IN_PROGRESS'; + ERROR[NO_TRANSACTION_IN_PROGRESS] = 'NO_TRANSACTION_IN_PROGRESS'; + ERROR[TRANSACTION_FAILURE] = 'TRANSACTION_FAILURE'; + ERROR[TRANSACTION_FAILURE_DURING_INIT] = 'TRANSACTION_FAILURE_DURING_INIT'; + ERROR[TRANSACTION_FAILURE_DURING_CLOSE_ALL] = 'TRANSACTION_FAILURE_DURING_CLOSE_ALL'; + ERROR[TRANSACTION_FAILURE_DURING_DESTROY] = 'TRANSACTION_FAILURE_DURING_DESTROY'; + ERROR[TRANSACTION_FAILURE_DURING_ROLLBACK] = 'TRANSACTION_FAILURE_DURING_ROLLBACK'; + + ERROR[DESTROY_FAILED_FILE_ERROR] = 'DESTROY_FAILED_FILE_ERROR'; + ERROR[DESTROY_FAILED_METADATA_REMOVAL_FAILURE] = 'DESTROY_FAILED_METADATA_REMOVAL_FAILURE'; + + ERROR[JSON_STORE_INVALID_JSON_STRUCTURE] = 'JSON_STORE_INVALID_JSON_STRUCTURE'; + ERROR[JSON_STORE_STORE_DATA_PROTECTION_KEY_FAILURE] = 'JSON_STORE_STORE_DATA_PROTECTION_KEY_FAILURE'; + ERROR[JSON_STORE_REMOVE_WITH_QUERIES_FAILURE] = 'JSON_STORE_REMOVE_WITH_QUERIES_FAILURE'; + ERROR[JSON_STORE_REPLACE_DOCUMENTS_FAILURE] = 'JSON_STORE_REPLACE_DOCUMENTS_FAILURE'; + ERROR[JSON_STORE_FILE_INFO_ERROR] = 'JSON_STORE_FILE_INFO_ERROR'; + ERROR[TRANSACTION_FAILURE_DURING_REMOVE_COLLECTION] = 'TRANSACTION_FAILURE_DURING_REMOVE_COLLECTION'; + ERROR[INVALID_SEARCH_FIELD_TYPES] = 'INVALID_SEARCH_FIELD_TYPES'; + ERROR[OPERATION_FAILED_ON_SPECIFIC_DOCUMENT] = 'OPERATION_FAILED_ON_SPECIFIC_DOCUMENT'; + ERROR[ACCEPT_CONDITION_FAILED] = 'ACCEPT_CONDITION_FAILED'; + ERROR[OFFSET_WITHOUT_LIMIT] = 'OFFSET_WITHOUT_LIMIT'; + ERROR[INVALID_LIMIT_OR_OFFSET] = 'INVALID_LIMIT_OR_OFFSET'; + ERROR[INVALID_USERNAME] = 'INVALID_USERNAME'; + ERROR[USERNAME_MISMATCH_DETECTED] = 'USERNAME_MISMATCH_DETECTED'; + ERROR[DESTROY_REMOVE_PERSISTENT_STORE_FAILED] = 'DESTROY_REMOVE_PERSISTENT_STORE_FAILED'; + ERROR[DESTROY_REMOVE_KEYS_FAILED] = 'DESTROY_REMOVE_KEYS_FAILED'; + ERROR[INVALID_KEY_ON_PROVISION] = 'INVALID_KEY_ON_PROVISION'; + ERROR[PROVISION_TABLE_SEARCH_FIELDS_MISMATCH] = 'PROVISION_TABLE_SEARCH_FIELDS_MISMATCH'; + ERROR[PERSISTENT_STORE_FAILURE] = 'PERSISTENT_STORE_FAILURE'; + ERROR[SUCCESS] = 'SUCCESS'; + ERROR[BAD_PARAMETER_EXPECTED_INT] = 'BAD_PARAMETER_EXPECTED_INT'; + ERROR[BAD_PARAMETER_EXPECTED_STRING] = 'BAD_PARAMETER_EXPECTED_STRING'; + ERROR[BAD_PARAMETER_EXPECTED_FUNCTION] = 'BAD_PARAMETER_EXPECTED_FUNCTION'; + ERROR[BAD_PARAMETER_EXPECTED_ALPHANUMERIC_STRING] = 'BAD_PARAMETER_EXPECTED_ALPHANUMERIC_STRING'; + ERROR[BAD_PARAMETER_EXPECTED_OBJECT] = 'BAD_PARAMETER_EXPECTED_OBJECT'; + ERROR[BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT] = 'BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT'; + ERROR[BAD_PARAMETER_EXPECTED_DOCUMENT] = 'BAD_PARAMETER_EXPECTED_DOCUMENT'; + ERROR[FAILED_TO_GET_UNPUSHED_DOCUMENTS_FROM_DB] = 'FAILED_TO_GET_UNPUSHED_DOCUMENTS_FROM_DB'; + ERROR[NO_ADAPTER_LINKED_TO_COLLECTION] = 'NO_ADAPTER_LINKED_TO_COLLECTION'; + ERROR[BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ARRAY_OF_DOCUMENTS] = 'BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ARRAY_OF_DOCUMENTS'; + ERROR[INVALID_PASSWORD_EXPECTED_ALPHANUMERIC_STRING_WITH_LENGTH_GREATER_THAN_ZERO] = 'INVALID_PASSWORD_EXPECTED_ALPHANUMERIC_STRING_WITH_LENGTH_GREATER_THAN_ZERO'; + ERROR[ADAPTER_FAILURE] = 'ADAPTER_FAILURE'; + ERROR[BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ID] = 'BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ID'; + ERROR[CAN_NOT_REPLACE_DEFAULT_FUNCTIONS] = 'CAN_NOT_REPLACE_DEFAULT_FUNCTIONS'; + ERROR[COULD_NOT_MARK_DOCUMENT_PUSHED] = 'COULD_NOT_MARK_DOCUMENT_PUSHED'; + ERROR[COULD_NOT_GET_SECURE_KEY] = 'COULD_NOT_GET_SECURE_KEY'; + ERROR[FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER] = 'FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER'; + ERROR[FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER_INVALID_LOAD_OBJ] = 'FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER_INVALID_LOAD_OBJ'; + ERROR[INVALID_KEY_IN_LOAD_OBJECT] = 'INVALID_KEY_IN_LOAD_OBJECT'; + ERROR[UNDEFINED_PUSH_OPERATION] = 'UNDEFINED_PUSH_OPERATION'; + ERROR[INVALID_ADD_INDEX_KEY] = 'INVALID_ADD_INDEX_KEY'; + ERROR[INVALID_SEARCH_FIELD] = 'INVALID_SEARCH_FIELD'; + ERROR[ERROR_CLOSING_ALL] = 'ERROR_CLOSING_ALL'; + ERROR[ERROR_CHANGING_PASSWORD] = 'ERROR_CHANGING_PASSWORD'; + ERROR[ERROR_DURING_DESTROY] = 'ERROR_DURING_DESTROY'; + ERROR[ERROR_CLEARING_COLLECTION] = 'ERROR_CLEARING_COLLECTION'; + ERROR[INVALID_PARAMETER_FOR_FIND_BY_ID] = 'INVALID_PARAMETER_FOR_FIND_BY_ID'; + ERROR[INVALID_SORT_OBJECT] = 'INVALID_SORT_OBJECT'; + ERROR[INVALID_FILTER_ARRAY] = 'INVALID_FILTER_ARRAY'; + ERROR[BAD_PARAMETER_EXPECTED_ARRAY_OF_OBJECTS] = 'BAD_PARAMETER_EXPECTED_ARRAY_OF_OBJECTS'; + ERROR[BAD_PARAMETER_EXPECTED_ARRAY_OF_CLEAN_DOCUMENTS] = 'BAD_PARAMETER_EXPECTED_ARRAY_OF_CLEAN_DOCUMENTS'; + ERROR[BAD_PARAMETER_WRONG_SEARCH_CRITERIA] = 'BAD_PARAMETER_WRONG_SEARCH_CRITERIA'; + + //public api + return { + PKG_NAME: 'wl.jsonstore', + ID_KEY: '_id', + JSON_DATA_KEY: 'json', + INDEXES_KEY: '_indexes', + ARRAY_JOIN: '-@-', + OPERATION_KEY: '_operation', + DEFAULT_USERNAME: 'jsonstore', + USERNAME: 'username', + DEFAULT_KEYCHAIN_USERNAME: 'jsonstorekey', + DEFAULT_ANDROID_KEYCHAIN_ID: 'dpk', + DELETED_KEY: '_deleted', + DIRTY_KEY: '_dirty', + OPERATION_DELETED: 'remove', + METADATA_TAG: '#dgonz.metadata', + KEYGEN_NOT_REQUIERED: 1, + NOT_FOUND: 'Not found', + ASCENDING: 'asc', + DESCENDING: 'desc', + QUERYPART: '$', + PERIOD: '.', + LESSTHAN: 'lessThan', + LESSOREQUALTHAN: 'lessOrEqualThan', + GREATERTHAN: 'greaterThan', + GREATEROREQUALTHAN: 'greaterOrEqualThan', + EQUAL: 'equal', + NOTEQUAL: 'notEqual', + INSIDE: 'inside', + NOTINSIDE: 'notInside', + BETWEEN: 'between', + NOTBETWEEN: 'notBetween', + RIGHTLIKE: 'rightLike', + NOTRIGHTLIKE: 'notRightLike', + LEFTLIKE: 'leftLike', + NOTLEFTLIKE: 'notLeftLike', + LIKE: 'like', + NOTLIKE: 'notLike', + ERROR: ERROR, + + //Exporting the error code contants + UNKNOWN_FAILURE: UNKNOWN_FAILURE, + OS_SECURITY_FAILURE : OS_SECURITY_FAILURE, + TRANSACTION_IN_PROGRESS: TRANSACTION_IN_PROGRESS, + FIPS_ENABLEMENT_FAILURE: FIPS_ENABLEMENT_FAILURE, + NO_TRANSACTION_IN_PROGRESS: NO_TRANSACTION_IN_PROGRESS, + TRANSACTION_FAILURE: TRANSACTION_FAILURE, + TRANSACTION_FAILURE_DURING_INIT: TRANSACTION_FAILURE_DURING_INIT, + TRANSACTION_FAILURE_DURING_CLOSE_ALL: TRANSACTION_FAILURE_DURING_CLOSE_ALL, + TRANSACTION_FAILURE_DURING_DESTROY: TRANSACTION_FAILURE_DURING_DESTROY, + TRANSACTION_FAILURE_DURING_REMOVE_COLLECTION: TRANSACTION_FAILURE_DURING_REMOVE_COLLECTION, + TRANSACTION_FAILURE_DURING_ROLLBACK: TRANSACTION_FAILURE_DURING_ROLLBACK, + + DESTROY_FAILED_FILE_ERROR : DESTROY_FAILED_FILE_ERROR, + DESTROY_FAILED_METADATA_REMOVAL_FAILURE : DESTROY_FAILED_METADATA_REMOVAL_FAILURE, + + JSON_STORE_INVALID_JSON_STRUCTURE: JSON_STORE_INVALID_JSON_STRUCTURE, + JSON_STORE_STORE_DATA_PROTECTION_KEY_FAILURE: JSON_STORE_STORE_DATA_PROTECTION_KEY_FAILURE, + JSON_STORE_REMOVE_WITH_QUERIES_FAILURE: JSON_STORE_REMOVE_WITH_QUERIES_FAILURE, + JSON_STORE_REPLACE_DOCUMENTS_FAILURE: JSON_STORE_REPLACE_DOCUMENTS_FAILURE, + JSON_STORE_FILE_INFO_ERROR: JSON_STORE_FILE_INFO_ERROR, + PERSISTENT_STORE_NOT_OPEN: PERSISTENT_STORE_NOT_OPEN, + INVALID_SEARCH_FIELD_TYPES: INVALID_SEARCH_FIELD_TYPES, + OPERATION_FAILED_ON_SPECIFIC_DOCUMENT: OPERATION_FAILED_ON_SPECIFIC_DOCUMENT, + ACCEPT_CONDITION_FAILED: ACCEPT_CONDITION_FAILED, + OFFSET_WITHOUT_LIMIT: OFFSET_WITHOUT_LIMIT, + INVALID_LIMIT_OR_OFFSET: INVALID_LIMIT_OR_OFFSET, + INVALID_USERNAME: INVALID_USERNAME, + USERNAME_MISMATCH_DETECTED: USERNAME_MISMATCH_DETECTED, + DESTROY_REMOVE_PERSISTENT_STORE_FAILED: DESTROY_REMOVE_PERSISTENT_STORE_FAILED, + DESTROY_REMOVE_KEYS_FAILED: DESTROY_REMOVE_KEYS_FAILED, + INVALID_KEY_ON_PROVISION: INVALID_KEY_ON_PROVISION, + PROVISION_TABLE_SEARCH_FIELDS_MISMATCH: PROVISION_TABLE_SEARCH_FIELDS_MISMATCH, + PERSISTENT_STORE_FAILURE: PERSISTENT_STORE_FAILURE, + SUCCESS: SUCCESS, + BAD_PARAMETER_EXPECTED_INT: BAD_PARAMETER_EXPECTED_INT, + BAD_PARAMETER_EXPECTED_STRING: BAD_PARAMETER_EXPECTED_STRING, + BAD_PARAMETER_EXPECTED_FUNCTION: BAD_PARAMETER_EXPECTED_FUNCTION, + BAD_PARAMETER_EXPECTED_ALPHANUMERIC_STRING: BAD_PARAMETER_EXPECTED_ALPHANUMERIC_STRING, + BAD_PARAMETER_EXPECTED_OBJECT: BAD_PARAMETER_EXPECTED_OBJECT, + BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT: BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT, + BAD_PARAMETER_EXPECTED_DOCUMENT: BAD_PARAMETER_EXPECTED_DOCUMENT, + FAILED_TO_GET_UNPUSHED_DOCUMENTS_FROM_DB: FAILED_TO_GET_UNPUSHED_DOCUMENTS_FROM_DB, + NO_ADAPTER_LINKED_TO_COLLECTION: NO_ADAPTER_LINKED_TO_COLLECTION, + BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ARRAY_OF_DOCUMENTS: BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ARRAY_OF_DOCUMENTS, + INVALID_PASSWORD_EXPECTED_ALPHANUMERIC_STRING_WITH_LENGTH_GREATER_THAN_ZERO: INVALID_PASSWORD_EXPECTED_ALPHANUMERIC_STRING_WITH_LENGTH_GREATER_THAN_ZERO, + ADAPTER_FAILURE: ADAPTER_FAILURE, + BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ID: BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ID, + CAN_NOT_REPLACE_DEFAULT_FUNCTIONS: CAN_NOT_REPLACE_DEFAULT_FUNCTIONS, + COULD_NOT_MARK_DOCUMENT_PUSHED: COULD_NOT_MARK_DOCUMENT_PUSHED, + COULD_NOT_GET_SECURE_KEY: COULD_NOT_GET_SECURE_KEY, + FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER: FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER, + FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER_INVALID_LOAD_OBJ: FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER_INVALID_LOAD_OBJ, + INVALID_KEY_IN_LOAD_OBJECT: INVALID_KEY_IN_LOAD_OBJECT, + UNDEFINED_PUSH_OPERATION: UNDEFINED_PUSH_OPERATION, + INVALID_ADD_INDEX_KEY: INVALID_ADD_INDEX_KEY, + INVALID_SEARCH_FIELD: INVALID_SEARCH_FIELD, + ERROR_CLOSING_ALL: ERROR_CLOSING_ALL, + ERROR_CHANGING_PASSWORD: ERROR_CHANGING_PASSWORD, + ERROR_DURING_DESTROY: ERROR_DURING_DESTROY, + ERROR_CLEARING_COLLECTION: ERROR_DURING_DESTROY, + INVALID_PARAMETER_FOR_FIND_BY_ID: INVALID_PARAMETER_FOR_FIND_BY_ID, + INVALID_SORT_OBJECT: INVALID_SORT_OBJECT, + INVALID_FILTER_ARRAY: INVALID_FILTER_ARRAY, + BAD_PARAMETER_EXPECTED_ARRAY_OF_OBJECTS: BAD_PARAMETER_EXPECTED_ARRAY_OF_OBJECTS, + BAD_PARAMETER_EXPECTED_ARRAY_OF_CLEAN_DOCUMENTS: BAD_PARAMETER_EXPECTED_ARRAY_OF_CLEAN_DOCUMENTS, + BAD_PARAMETER_WRONG_SEARCH_CRITERIA: BAD_PARAMETER_WRONG_SEARCH_CRITERIA + }; + +})(); //end WL.constant + +/** +Provides some validation methods +@private +**/ +WL.check = (function(jQuery, underscore) { + + 'use strict'; + + //Dependencies + var constant = WL.constant, + _ = underscore, + $ = jQuery, + + //Support for isArray on older browsers + isArray = Array.isArray || function(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; + + var __isArray = function(input) { + return isArray(input); + }; + + var __isEmptyArray = function(arr) { + return (__isArray(arr) && arr.length === 0); + }; + + //Add pick! + var __filterPick = function(obj, keys, iterator, context) { + var results = []; + var parts = []; + if (obj === null) { + return results; + } + _.each(obj, function(value, index, list) { + keys.unshift(value); + if (iterator.call(context, value, index, list)) { + results[results.length] = _.pick.apply(context, keys); + } + }); + + if (!__isUndefined(context.filter)) { + _.each(results, function(value, index) { + parts[0] = _.pick(value, context.filter); //filter through json created such as _id, _operation, _deleted, etc... + parts[1] = _.pick(value.json, context.filter); //filter through json array + parts[2] = _.pick(value._indexes, context.filter); //filter through _indexes array + results[index] = _removeArrayComponents(_.merge(parts[0], parts[1], parts[2])); + }); + } + results = _removeDuplicates(results); + return results; + }; + + var _removeArrayComponents = function(arr) { + var keys = _.keys(arr); + _.each(keys, function(value) { + if (value !== constant.JSON_DATA_KEY && __isArray(arr[value]) && arr[value].length < 2) { + arr[value] = '' + arr[value][0]; + } else if (value !== constant.JSON_DATA_KEY && __isArray(arr[value]) && arr[value].length > 1) { + arr[value] = arr[value].join(constant.ARRAY_JOIN); + } else if (value === constant.DELETED_KEY) { + arr[value] = (arr[value] ? '1' : '0'); + } + }); + return arr; + }; + + var _removeDuplicates = function(arr) { + var map = []; + var newResults = []; + _.each(arr, function(value) { + + if (__isUndefined(map[JSON.stringify(value)])) { + if (!__isUndefined(value._indexes)) { + delete value._indexes; + } + map[JSON.stringify(value)] = true; + newResults.push(value); + } + }); + return newResults; + }; + //Constants + var ALPHANUMERIC_REGEX = /^[a-z0-9]+$/i; + + /** Checks if a string is alphanumeric. + @private + */ + var __isAlphaNumeric = function(string) { + + var result = ALPHANUMERIC_REGEX.exec(string); + + return (typeof string === 'string' && isArray(result) && result.length > 0 && result[0] === string); + }; + + /** Checks if input is an object. + Note: __isObject([]) = false by default, + if isArrayValid is true it returns true + @private + */ + var __isObject = function(input, obj) { + + obj = obj || {}; + + var isArrayValidType = obj.isArrayValid || false; + + if (typeof input === 'undefined' || typeof input !== 'object' || input === null) { + return false; + } + + if (isArray(input) && !isArrayValidType) { + return false; + } + + return true; + }; + + /** Checks if an object does not contain other objects or arrays. + @private + */ + var __isSimpleObject = function(obj) { + + var key, + hasOwn = Object.prototype.hasOwnProperty; + + if (!__isObject(obj)) { + return false; + } + + for (key in obj) { + if (hasOwn.call(obj, key) && __isObject(obj[key], { + isArrayValid: true + }) || obj[key] === null) { + return false; + } + } + return true; + }; + + /** Private function to check if the array contains only objects and arrays. + @private + */ + var __isArrayOfObjects = function(arr) { + + var i, + len; + + if (!isArray(arr) || arr.length < 1) { + return false; + } + + for (i = 0, len = arr.length; i < len; i++) { + if (!__isObject(arr[i])) { + return false; + } + } + + return true; + }; + + /** Private function to check if an object contains duplicate keys, this will return true: + {fn: 'carlos', fN: 'Carlos', Fn: 'clos', FN: 'carlitos'}) + @private + */ + var __containsDuplicateKeys = function(obj) { + + var i, + len, + keys = [], + key, + hasOwn = Object.prototype.hasOwnProperty; + + if (!__isObject(obj)) { + // fail the test, because it's not a valid object + return true; + } + + for (key in obj) { + if (hasOwn.call(obj, key)) { + keys.push(key.toLowerCase()); + } + } + + keys = keys.sort(); + + //Guard against the {} case + if (keys.length <= 0) { + return false; + } + + for (i = 0, len = keys.length; i < len - 1; i++) { + if (keys[i + 1] === keys[i]) { + return true; + } + } + + return false; //all green! + }; + + /** Checks if an object is a valid adapter. + @private + */ + var __isValidAdapter = function(obj) { + + return __isObject(obj) && + typeof obj.name === 'string' && + obj.name.length > 0; + }; + + /** Checks if the input (num) is an integer. + @private + */ + var __isInt = function(num) { + + return (typeof num === 'number' && parseFloat(num) === parseInt(num, 10) && !isNaN(num)); + }; + + /** Checks if the input (arrray) are all integers. + @private + */ + var __isArrayOfInts = function(intArr) { + var i, + len; + + if (!isArray(intArr) || intArr.length < 1) { + return false; + } + + for (i = 0, len = intArr.length; i < len; i++) { + if (!__isInt(intArr[i])) { + return false; + } + } + + return true; + }; + + /** Checks if the input (arrray) are all documents. + @private + */ + var __isArrayOfDocuments = function(docArr) { + var i, + len; + + if (!isArray(docArr) || docArr.length < 1) { + return false; + } + + for (i = 0, len = docArr.length; i < len; i++) { + if (!__isValidDocument(docArr[i])) { + return false; + } + } + + return true; + }; + + /** Checks if the input (array) are clean objects thath include _id, json, operation, deleted, and dirty + @private + */ + var __isArrayOfCleanDocuments = function(docArr) { + var i, + len; + + if (isArray(docArr) && docArr.length === 0) { + return true; + } else if (!isArray(docArr) || docArr.length < 1) { + return false; + } + + for (i = 0, len = docArr.length; i < len; i++) { + if (!__isValidDocument(docArr[i]) && !__isString(docArr[i][constant.OPERATION_KEY]) && !__isString(docArr[i][constant.DIRTY_KEY]) && !__isBoolean(docArr[i][constant.DELETED_KEY])) { + return false; + } + } + + return true; + }; + + /** Checks if the (input) object has json specific searchfields (ie: _deleted, json) + Valid input : ['_id', 'json', '_deleted', '_operation', '_dirty'] + @private + */ + var __hasJSONSearchFields = function(arr) { + var jsonFields = ['_id', 'json', '_deleted', '_operation', '_dirty']; + + return (_.intersection(arr, jsonFields).length > 0); + + }; + + /** Checks if the object passed is a valid document. + Valid object: {_id: 0, json: {...}} + @private + */ + var __isValidDocument = function(doc) { + + return (__isObject(doc) && + __isInt(Number(doc[constant.ID_KEY])) && + __isObject(doc[constant.JSON_DATA_KEY])); + }; + + /** Checks if func is a function + @private + */ + var __isFunction = function(func) { + return typeof func === 'function'; + }; + + /** Checks if str is a string + @private + */ + var __isString = function(str) { + return typeof str === 'string'; + }; + + /** Checks if input is undefined (or null) + @private + */ + var __isUndefined = function(input) { + return (typeof input === 'undefined' || input === null); + }; + + /** Checks if input is a boolean + @private + */ + var __isBoolean = function(input) { + return typeof input === 'boolean'; + }; + + /** Checks if input is a compatible boolean + Valid Example : '1', '0', 1, 0, true, false + @private + */ + var __isCompatibleBoolean = function(input) { + if ((typeof input === 'boolean') || + (__isInt(input) && input === 1 || input === 0) || + (__isString(input) && input === '1' || input === '0')) { + return true; + } else { + return false; + } + }; + + /** Checks if valid load object + Valid Example: { procedure: 'getCustomers', params: [], key: "customers" } + @private + */ + var __isValidLoadObject = function(obj) { + + if (!__isObject(obj)) { + return false; + } + + return (__isString(obj.procedure) && isArray(obj.params) && __isString(obj.key)); + }; + + /** Merges to objects + var obj1 = {fn: 'carlos'}, var obj2 = {ln: 'andreu'}; + mergeObjects(obj1,obj2) => {fn: 'carlos', ln: 'andreu'} + @private + */ + var __mergeObjects = function(obj1, obj2) { + if (!__isObject(obj1) || !__isObject(obj2)) { + return -1; + } + + //prevent corrupting obj references + var cloneOb1 = _.cloneDeep(obj1); + var cloneOb2 = _.cloneDeep(obj2); + + return $.extend(cloneOb1, cloneOb2); + + }; + + /** Checks if it's part of the searchFields, if you pass additionalSearchFields + it will also check those and only return true if it's inside one of those objects. + @private + */ + var __isPartofSearchFields = function(obj, searchFields, additionalSearchFields) { + + if (!__isSimpleObject(obj) || !__isSimpleObject(searchFields)) { + return false; + } + + var key, + hasOwn = Object.prototype.hasOwnProperty, + allSearchFields = searchFields; + + //We want to make _id a valid searchField + allSearchFields[constant.ID_KEY] = 'number'; + + if (!__isUndefined(additionalSearchFields)) { + allSearchFields = __mergeObjects(searchFields, additionalSearchFields); + } + + //search searchFields in obj + for (key in obj) { + if (hasOwn.call(obj, key) && __isUndefined(allSearchFields[key.toLocaleLowerCase()])) { + return false; + } + } + + return true; + }; + + /** Counts how many keys an object has + @private + */ + var __countKeys = function(obj) { + + if (!__isObject(obj)) { + return -1; + } + + //This is faster but not supported everywhere + if (!__isUndefined(Object.keys)) { + + return Object.keys(obj).length; + + //Loop over the keys and count them + } else { + var key, + hasOwn = Object.prototype.hasOwnProperty, + count = 0; + + for (key in obj) { + if (hasOwn.call(obj, key)) { + count++; + } + } + + return count; + } + }; + + /** Checks if a reserved word is used + @private + */ + var __isReservedWord = function(username) { + + if (!__isString(username)) { + return false; + } + + var usr = username.toLowerCase(); + + return (usr.indexOf(constant.DEFAULT_USERNAME) === 0 || + usr.indexOf(constant.DEFAULT_KEYCHAIN_USERNAME) === 0 || + usr.indexOf(constant.DEFAULT_ANDROID_KEYCHAIN_ID) === 0); + }; + + /** Checks if the search fields object has valid types + @private + */ + var __isValidSchemaObject = function(obj) { + + var key, + hasOwn = Object.prototype.hasOwnProperty, + invalidKeys = [constant.ID_KEY, constant.OPERATION_KEY, constant.DELETED_KEY, constant.DIRTY_KEY, constant.JSON_DATA_KEY], + validTypes = ['string', 'integer', 'boolean', 'number']; + + for (key in obj) { + + if (hasOwn.call(obj, key) && validTypes.indexOf(obj[key]) === -1 || invalidKeys.indexOf(key) > -1) { + return false; + } + } + return true; + }; + + /** Checks if n is a number + @private + */ + var __isNumber = function(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + + /** Checks if sortObj is a valid sort object for a query + @private + */ + var __isValidSortObject = function(sortObj, searchFields, additionalSearchFields) { + var propertiesValidated = 0, + sortObjKey, sortStr; + + for (sortObjKey in sortObj) { + if (sortObj.hasOwnProperty(sortObjKey)) { + if (propertiesValidated > 0) { + WL.Logger.trace('Sort object ' + JSON.stringify(sortObj) + ' has more than one property. Each object must have only one property.'); + return false; + } + //check is sortObjKey is lowerCase + if (_.isUndefined(searchFields[sortObjKey.toLowerCase()]) && _.isUndefined(additionalSearchFields[sortObjKey.toLowerCase()])) { + WL.Logger.trace('Sort key ' + sortObjKey + ' is not part of search fields: ' + JSON.stringify(searchFields) + ' or additional search fields: ' + JSON.stringify(additionalSearchFields)); + return false; + } + + sortStr = sortObj[sortObjKey]; + + //Check that the string that specifies sorting order says either "asc" or "desc" + if (__isString(sortStr) && sortStr.length > 0 && (/^(a|de)sc$/i.test(sortStr))) { + WL.Logger.trace('Sort key ' + sortObjKey + 'is not one of the valid strings.'); + propertiesValidated++; + } else { + return false; + } + } + } + + if (propertiesValidated === 0) { + return false; + } + + return true; + }; + + var __isArrayOfSAFields = function(filterArray, allSearchFields, allAdditionalSearchFields) { + + if (!__isArray(filterArray)) { + return false; + } + + var len = filterArray.length, + allSearchFieldsKeys = _.keys(allSearchFields), + allAdditionalSearchFieldsKeys = _.keys(allAdditionalSearchFields), + jsonstoreKeys = [constant.ID_KEY, constant.JSON_DATA_KEY, + constant.DIRTY_KEY, constant.DELETED_KEY, + constant.OPERATION_KEY + ], + current; + + while (len--) { + + current = filterArray[len]; + + if (!_.contains(allSearchFieldsKeys, current) && !_.contains(allAdditionalSearchFieldsKeys, current) && !_.contains(jsonstoreKeys, current)) { + + return false; + } + } + + return true; + }; + + //public api + return { + isAlphaNumeric: __isAlphaNumeric, + isObject: __isObject, + isSimpleObject: __isSimpleObject, + isArrayOfObjects: __isArrayOfObjects, + containsDuplicateKeys: __containsDuplicateKeys, + isValidAdapter: __isValidAdapter, + isInt: __isInt, + isValidDocument: __isValidDocument, + isFunction: __isFunction, + isString: __isString, + isUndefined: __isUndefined, + isBoolean: __isBoolean, + isCompatibleBoolean: __isCompatibleBoolean, + isValidLoadObject: __isValidLoadObject, + isPartofSearchFields: __isPartofSearchFields, + countKeys: __countKeys, + mergeObjects: __mergeObjects, + isReservedWord: __isReservedWord, + isArrayOfInts: __isArrayOfInts, + isValidSchemaObject: __isValidSchemaObject, + isNumber: __isNumber, + filterPick: __filterPick, + isArrayOfDocuments: __isArrayOfDocuments, + isArray: __isArray, + isEmptyArray: __isEmptyArray, + isValidSortObject: __isValidSortObject, + isArrayOfSAFields: __isArrayOfSAFields, + isArrayOfCleanDocuments: __isArrayOfCleanDocuments, + hasJSONSearchFields: __hasJSONSearchFields + }; + +})(WLJQ, WL_); //end WL.check + +/** +Provides support for setting up callbacks with events. +@private +**/ +WL.callback = (function(jQuery) { + + 'use strict'; + + //Dependencies + var check = WL.check, + constant = WL.constant, + $ = jQuery; + + /** + Error object returned by rejected deferreds + @private + @param obj {object} src = source of the error, + err = error int, col = collection name, usr = username, + doc = doc that failed and res = response from the server. + */ + var ErrorObject = function(obj) { + this.src = obj.src || ''; + this.err = obj.err || constant.UNKNOWN_FAILURE; + this.msg = WL.JSONStore.getErrorMessage(this.err); + this.col = obj.col || ''; + this.usr = obj.usr || constant.DEFAULT_USERNAME; + this.doc = obj.doc || {}; + this.res = obj.res || {}; + }; + + ErrorObject.prototype.toString = function() { + return JSON.stringify(this, null, ' '); + }; + + //Private Members + /*jshint maxparams:6*/ + var _generate = function(options, events, src, collectionName, username, deferred) { + + var success, + failure; + + success = function(data, more) { + + //Push is a special case we handle separately, search for __push + if (!check.isUndefined(deferred) && 'push' !== src) { + deferred.resolve(data, more); + } + + //Send the WL/JSONSTORE/SUCCESS event + $(document.body).trigger(events.success, [data, src, collectionName, more, username]); + + //Call the user provided callback if there is one + if (check.isObject(options) && check.isFunction(options.onSuccess)) { + options.onSuccess(data, more); + } + }, + + failure = function(data, more) { + + var errorObject = { + src: src, + col: collectionName, + usr: username + }, + realErrorObject; + + if (check.isInt(data)) { + errorObject.err = data; + errorObject.msg = WL.JSONStore.getErrorMessage(data); + } else { + //We got a document back instead of an error code + errorObject.doc = data; + errorObject.err = -11; + errorObject.msg = WL.JSONStore.getErrorMessage(-11); + } + + realErrorObject = new ErrorObject(errorObject); + + //Resolve with an error object + if (!check.isUndefined(deferred)) { + deferred.reject(realErrorObject); + } + + WL.Logger.ctx({ + pkg: constant.PKG_NAME + }).error(realErrorObject); + + //Send the WL/JSONSTORE/FAILURE event + $(document.body).trigger(events.failure, [data, src, collectionName, more, username]); + + //Call the user provided callback if there is one + if (check.isObject(options) && check.isFunction(options.onFailure)) { + options.onFailure(data, more); + } + }; + + return { + onSuccess: success, + onFailure: failure + }; + }; + + //public api + return { + generate: _generate, + ErrorObject: ErrorObject + }; + +})(WLJQ); //end WL.callback + +/** +Provides a way to traverse JSON objects in JavaScript +@private +**/ +WL.jspath = (function(jQuery) { + + 'use strict'; + + var $ = jQuery, + check = WL.check, + + __locate = function(element, key) { + + var arr = []; + + function traverse(currentKey, curretValue, parentPath) { + + if (check.isObject(curretValue, { + isArrayValid: true + }) && !check.isUndefined(curretValue)) { + + if (!check.isUndefined(currentKey)) { + parentPath.push(currentKey); + } + + $.map(curretValue, function(v, k) { + + if (key === k) { + arr.push({ + parent: parentPath.join('.').replace('.', ''), + key: k, + value: v + }); + } + + traverse(k, v, parentPath); + }); + + if (!check.isUndefined(currentKey)) { + parentPath.pop(); + } + } + } + + traverse('', element, []); + + return arr; + }, + + __get = function(data, element, parent) { + + var databack, + arr = [], + currentParent; + + if (check.isUndefined(parent)) { + parent = 'root'; + } + + if (!check.isObject(data, { + isArrayValid: true + }) || !check.isString(element) || !check.isString(parent)) { + + return []; + } + + if (parent === 'root') { + return [data[element]]; + } + + databack = __locate(data, element); + + for (var i = 0; i < databack.length; i++) { + currentParent = databack[i].parent; + if (currentParent.indexOf(parent) >= 0) { + arr.push(databack[i].value); + } + } + + return arr; + }; + + //public api + return { + get: __get + }; + +})(WLJQ); //end WL.jspath + +/** +Provide iterators for advancedFind +@private +**/ +WL.findIterator = (function(underscore) { + + 'use strict'; + + var check = WL.check, + constant = WL.constant, + _ = underscore, + __inequalityComparator = function(value, index, list) { + var self = this, + sc, + sv, + flag = false, + pattLeft, + pattRight, + hasOwn = Object.prototype.hasOwnProperty; + _.each(list, function(iSearch, vSearch) { + if (check.isCompatibleBoolean(self.searchStr[vSearch])) { + if (check.isString(self.searchStr[vSearch])) { + self.searchStr[vSearch] = (self.searchStr[vSearch] === '1' ? 1 : 0); + } else { + self.searchStr[vSearch] = (self.searchStr[vSearch] ? 1 : 0); + } + } + if (check.isCompatibleBoolean(iSearch[0])) { + iSearch[0] = (iSearch[0] === '0' ? 0 : iSearch[0]); + iSearch[0] = (iSearch[0] === '1' ? 1 : iSearch[0]); + } + if (hasOwn.call(self.searchStr, vSearch) && (check.isNumber(iSearch[0]) || check.isString(iSearch[0]))) { + sc = '' + iSearch[0]; + sv = '' + self.searchStr[vSearch]; + pattLeft = new RegExp('(' + self.searchStr[vSearch] + '$)', ['ig']); + pattRight = new RegExp('(^' + self.searchStr[vSearch] + ')', ['ig']); + if (self.kLower === constant.LEFTLIKE.toLowerCase()) { + if (sc.match(pattLeft) !== null) { + flag = true; + } + } else if (self.kLower === constant.NOTLEFTLIKE.toLowerCase()) { + if (sc.match(pattLeft) === null) { + flag = true; + } + } else if (self.kLower === constant.RIGHTLIKE.toLowerCase()) { + if (sc.match(pattRight) !== null) { + flag = true; + } + } else if (self.kLower === constant.NOTRIGHTLIKE.toLowerCase()) { + if (sc.match(pattRight) === null) { + flag = true; + } + } else if (self.kLower === constant.LIKE.toLowerCase() && sc.toLowerCase().indexOf(sv.toLowerCase()) > -1) { + flag = true; + } else if (self.kLower === constant.NOTLIKE.toLowerCase() && sc.toLowerCase().indexOf(sv.toLowerCase()) === -1) { + flag = true; + } else if (self.kLower === constant.EQUAL.toLowerCase() && iSearch[0] === self.searchStr[vSearch]) { + flag = true; + } else if (self.kLower === constant.NOTEQUAL.toLowerCase() && iSearch[0] !== self.searchStr[vSearch]) { + flag = true; + } + } + if (hasOwn.call(self.searchStr, vSearch) && check.isNumber(iSearch[0]) && !flag) { + if (self.kLower === constant.LESSTHAN.toLowerCase() && iSearch[0] < self.searchStr[vSearch]) { + flag = true; + } else if (self.kLower === constant.LESSOREQUALTHAN.toLowerCase() && iSearch[0] <= self.searchStr[vSearch]) { + flag = true; + } else if (self.kLower === constant.GREATERTHAN.toLowerCase() && iSearch[0] > self.searchStr[vSearch]) { + flag = true; + } else if (self.kLower === constant.GREATEROREQUALTHAN.toLowerCase() && iSearch[0] >= self.searchStr[vSearch]) { + flag = true; + } else { + flag = false; + } + } + + if (hasOwn.call(self.searchStr, vSearch) && check.isCompatibleBoolean(iSearch[0]) && !flag) { + if (self.kLower === constant.LESSTHAN.toLowerCase() && iSearch[0] < self.searchStr[vSearch]) { + flag = true; + } else if (self.kLower === constant.LESSOREQUALTHAN.toLowerCase() && iSearch[0] <= self.searchStr[vSearch]) { + flag = true; + } else if (self.kLower === constant.GREATERTHAN.toLowerCase() && iSearch[0] > self.searchStr[vSearch]) { + flag = true; + } else if (self.kLower === constant.GREATEROREQUALTHAN.toLowerCase() && iSearch[0] >= self.searchStr[vSearch]) { + flag = true; + /*jshint eqeqeq:false*/ + } else if (self.kLower === constant.EQUAL.toLowerCase() && iSearch[0] == self.searchStr[vSearch]) { + flag = true; + } else if (self.kLower === constant.NOTEQUAL.toLowerCase() && iSearch[0] !== self.searchStr[vSearch]) { + flag = true; + } else { + flag = false; + } + } + }); + return flag; + }, + + __rangeComparator = function(value, index, list) { + var self = this, + flag = false, + hasOwn = Object.prototype.hasOwnProperty; + _.each(list, function(iSearch, vSearch) { + if (!check.isUndefined(self.searchStr[vSearch]) && + check.isCompatibleBoolean(self.searchStr[vSearch][0]) && check.isCompatibleBoolean(self.searchStr[vSearch][1])) { + if (check.isString(self.searchStr[vSearch][0]) && check.isString(self.searchStr[vSearch][1])) { + self.searchStr[vSearch][0] = (self.searchStr[vSearch][0] === '1' ? 1 : 0); + self.searchStr[vSearch][1] = (self.searchStr[vSearch][1] === '1' ? 1 : 0); + } else { + self.searchStr[vSearch][0] = (self.searchStr[vSearch][0] ? 1 : 0); + self.searchStr[vSearch][1] = (self.searchStr[vSearch][1] ? 1 : 0); + } + } + if (check.isCompatibleBoolean(iSearch[0])) { + iSearch[0] = (iSearch[0] === '0' ? 0 : iSearch[0]); + iSearch[0] = (iSearch[0] === '1' ? 1 : iSearch[0]); + } + + if (hasOwn.call(self.searchStr, vSearch) && (check.isNumber(iSearch[0]) || check.isString(iSearch[0]) || check.isCompatibleBoolean(iSearch[0]))) { + if (self.kLower === constant.INSIDE.toLowerCase() && _.contains(self.searchStr[vSearch], iSearch[0])) { + flag = true; + } else if (self.kLower === constant.NOTINSIDE.toLowerCase() && !_.contains(self.searchStr[vSearch], iSearch[0])) { + flag = true; + } else if (self.kLower === constant.BETWEEN.toLowerCase() && self.searchStr[vSearch].length !== 2) { + flag = true; //syntax error (ignore comparator) + } else if (self.kLower === constant.BETWEEN.toLowerCase() && + (iSearch[0] >= self.searchStr[vSearch][0] && iSearch[0] <= self.searchStr[vSearch][1])) { + flag = true; + } else if (self.kLower === constant.NOTBETWEEN.toLowerCase() && !(iSearch[0] >= self.searchStr[vSearch][0] && iSearch[0] <= self.searchStr[vSearch][1])) { + flag = true; + } else if (self.kLower === constant.NOTBETWEEN.toLowerCase() && self.searchStr[vSearch].length !== 2) { + flag = true; //syntax error (ignore comparator) + } else { + flag = false; + } + + } + }); + + return flag; + }, + + __get = function(iteratorName) { + + var map = { + lessthan: __inequalityComparator, + lessorequalthan: __inequalityComparator, + greaterthan: __inequalityComparator, + greaterorequalthan: __inequalityComparator, + equal: __inequalityComparator, + notequal: __inequalityComparator, + inside: __rangeComparator, + notinside: __rangeComparator, + between: __rangeComparator, + notbetween: __rangeComparator, + leftlike: __inequalityComparator, + notleftlike: __inequalityComparator, + rightlike: __inequalityComparator, + notrightlike: __inequalityComparator, + notlike: __inequalityComparator, + like: __inequalityComparator + }; + + return map[iteratorName]; + }; + + //public api + return { + get: __get + }; + +})(WL_); //end WL.findIterator + +if (WL.Client.getEnvironment() === WL.Environment.IPHONE || + WL.Client.getEnvironment() === WL.Environment.IPAD || + WL.Client.getEnvironment() === WL.Environment.ANDROID || + WL.Client.getEnvironment() === WL.Environment.WINDOWS8 || + WL.Client.getEnvironment() === WL.Environment.WINDOWS || + WL.Client.getEnvironment() === WL.Environment.WINDOWSPHONE8) { + + /** + Provides access to our internal storage via cordova. + Internally used in WL.JSONStore. + @private + **/ + WL.db = (function(jQuery) { + + 'use strict'; + + //dependencies + var cdv = cordova, + $ = jQuery, + + //constants + STORAGE_PLUGIN = 'StoragePlugin', + PROVISION_METHOD = 'provision', + STORE_METHOD = 'store', + FIND_METHOD = 'find', + FIND_BY_ID_METHOD = 'findById', + ADVANCED_FIND_METHOD = 'advancedFind', + REPLACE_METHOD = 'replace', + REMOVE_METHOD = 'remove', + ALL_DIRTY_METHOD = 'allDirty', + CLEAN_METHOD = 'markClean', + QUEUE_COUNT_METHOD = 'localCount', + COLLECTION_COUNT_METHOD = 'count', + IS_DIRTY_METHOD = 'isDirty', + DROP_TABLE_METHOD = 'dropTable', + CHANGE_PW_METHOD = 'changePassword', + CLOSE_METHOD = 'closeDatabase', + DESTROY_METHOD = 'destroyDbFileAndKeychain', + IS_KEY_GEN_REQ_METHOD = 'isKeyGenRequired', + START_TRANSACTION_METHOD = 'startTransaction', + COMMIT_TRANSACTION_METHOD = 'commitTransaction', + ROLLBACK_TRANSACTION_METHOD = 'rollbackTransaction', + CHANGE_METHOD = 'change', + CLEAR_METHOD = 'clear', + FILE_INFO_METHOD = 'fileInfo', + SYNC_METHOD = 'sync', + + __callNative = function(args, options, pluginName, nativeFunction) { + cdv.exec(options.onSuccess, options.onFailure, pluginName, nativeFunction, args); + }, + + _provision = function(collection, searchFields, options) { + + __callNative([collection, searchFields, options], options, STORAGE_PLUGIN, PROVISION_METHOD); + }, + + _store = function(collection, data, options) { + + __callNative([collection, data, options], options, STORAGE_PLUGIN, STORE_METHOD); + }, + + _find = function(collection, query, options) { + + __callNative([collection, query, options], options, STORAGE_PLUGIN, FIND_METHOD); + }, + + _sync = function(collection,options){ + __callNative([collection], options, STORAGE_PLUGIN, SYNC_METHOD); + }, + + _findById = function(collection, id, options) { + __callNative([collection, id], options, STORAGE_PLUGIN, FIND_BY_ID_METHOD); + }, + _advancedFind = function(collection, query, options) { + + __callNative([collection, query, options], options, STORAGE_PLUGIN, ADVANCED_FIND_METHOD); + }, + + _replace = function(collection, doc, options) { + + __callNative([collection, doc, options], options, STORAGE_PLUGIN, REPLACE_METHOD); + }, + + _remove = function(collection, query, options) { + + __callNative([collection, query, options], options, STORAGE_PLUGIN, REMOVE_METHOD); + }, + + _allDirty = function(collection, docs, options) { + __callNative([collection, docs], options, STORAGE_PLUGIN, ALL_DIRTY_METHOD); + }, + + _clean = function(collection, documents) { + //The method was changed to use promises instead of callbacks. + var deferred = $.Deferred(), + options = { + onSuccess: function(rc) { + deferred.resolve(rc); + }, + onFailure: function(err) { + deferred.reject(err); + } + }; + + __callNative([collection, documents], options, STORAGE_PLUGIN, CLEAN_METHOD); + return deferred.promise(); + }, + + _localCount = function(collection, options) { + + __callNative([collection], options, STORAGE_PLUGIN, QUEUE_COUNT_METHOD); + }, + + _count = function(collection, options, query) { + + __callNative([collection, query, options], options, STORAGE_PLUGIN, COLLECTION_COUNT_METHOD); + }, + + _isDirty = function(collection, docId, options) { + + __callNative([collection, docId], options, STORAGE_PLUGIN, IS_DIRTY_METHOD); + }, + + _clear = function(collection, options) { + + __callNative([collection], options, STORAGE_PLUGIN, DROP_TABLE_METHOD); + }, + + _change = function(collection, data, options) { + + __callNative([collection, data, options], options, STORAGE_PLUGIN, CHANGE_METHOD); + }, + + _clearCollection = function(collection, options) { + __callNative([collection], options, STORAGE_PLUGIN, CLEAR_METHOD); + }, + + _changePW = function(oldPw, newPw, username, options) { + + __callNative([oldPw, newPw, username], options, STORAGE_PLUGIN, CHANGE_PW_METHOD); + oldPw = null; + newPw = null; + }, + + _closeDatabase = function(options) { + + __callNative([], options, STORAGE_PLUGIN, CLOSE_METHOD); + }, + + _destroy = function(options, username) { + + __callNative([username], options, STORAGE_PLUGIN, DESTROY_METHOD); + }, + + _isKeyGenReq = function(username, callback) { + + var options = { + onSuccess: callback, + onFailure: callback + }; + + __callNative([username], options, STORAGE_PLUGIN, IS_KEY_GEN_REQ_METHOD); + }, + + _startTransaction = function(callbacks) { + __callNative([], callbacks, STORAGE_PLUGIN, START_TRANSACTION_METHOD); + }, + + _commitTransaction = function(callbacks) { + __callNative([], callbacks, STORAGE_PLUGIN, COMMIT_TRANSACTION_METHOD); + }, + + _rollbackTransaction = function(callbacks) { + __callNative([], callbacks, STORAGE_PLUGIN, ROLLBACK_TRANSACTION_METHOD); + }, + + _fileInfo = function(callbacks) { + __callNative([], callbacks, STORAGE_PLUGIN, FILE_INFO_METHOD); + }, + + win8Plugin; + if (WL.Client.getEnvironment() === WL.Environment.WINDOWS8 || WL.Client.getEnvironment() === WL.Environment.WINDOWS || WL.Client.getEnvironment() === WL.Environment.WINDOWSPHONE8 ) { + var base64 = cdv.require('cordova/base64'); + win8Plugin = new JSONStoreWin8.StoragePlugin(); + + __callNative = function(args, options, pluginName, nativeFunction) { + var onSuccess = options.onSuccess, + onFailure = options.onFailure; + + var resultHandler = function(result) { + if (result.isSuccess) { + onSuccess(JSON.parse(result.value)); + } else { + onFailure(JSON.parse(result.value)); + } + }; + + for (var n = 0; n < args.length; n++) { + if (args[n] instanceof ArrayBuffer) { + args[n] = base64.fromArrayBuffer(args[n]); + } + + if (typeof args[n] !== 'string') { + args[n] = JSON.stringify(args[n]); + } + } + + switch (nativeFunction) { + case PROVISION_METHOD: + win8Plugin.provision(JSON.stringify(args)).done(resultHandler); + break; + case STORE_METHOD: + win8Plugin.store(JSON.stringify(args)).done(resultHandler); + break; + case FIND_METHOD: + win8Plugin.find(JSON.stringify(args)).done(resultHandler); + break; + case FIND_BY_ID_METHOD: + win8Plugin.findById(JSON.stringify(args)).done(resultHandler); + break; + case REPLACE_METHOD: + win8Plugin.replace(JSON.stringify(args)).done(resultHandler); + break; + case REMOVE_METHOD: + win8Plugin.remove(JSON.stringify(args)).done(resultHandler); + break; + case ALL_DIRTY_METHOD: + win8Plugin.allDirty(JSON.stringify(args)).done(resultHandler); + break; + case CLEAN_METHOD: + win8Plugin.markClean(JSON.stringify(args)).done(resultHandler); + break; + case QUEUE_COUNT_METHOD: + win8Plugin.localCount(JSON.stringify(args)).done(resultHandler); + break; + case COLLECTION_COUNT_METHOD: + win8Plugin.count(JSON.stringify(args)).done(resultHandler); + break; + case IS_DIRTY_METHOD: + win8Plugin.isDirty(JSON.stringify(args)).done(resultHandler); + break; + case DROP_TABLE_METHOD: + win8Plugin.dropTable(JSON.stringify(args)).done(resultHandler); + break; + case IS_KEY_GEN_REQ_METHOD: + win8Plugin.isKeyGenRequired(JSON.stringify(args)).done(resultHandler); + break; + case CHANGE_PW_METHOD: + win8Plugin.changePassword(JSON.stringify(args)).done(resultHandler); + break; + case CLOSE_METHOD: + win8Plugin.closeDatabase(JSON.stringify(args)).done(resultHandler); + break; + case DESTROY_METHOD: + win8Plugin.destroyDbFileAndKeychain(JSON.stringify(args)).done(resultHandler); + break; + case CHANGE_METHOD: + win8Plugin.change(JSON.stringify(args)).done(resultHandler); + break; + case CLEAR_METHOD: + win8Plugin.clear(JSON.stringify(args)).done(resultHandler); + break; + case START_TRANSACTION_METHOD: + win8Plugin.startTransaction(JSON.stringify(args)).done(resultHandler); + break; + case COMMIT_TRANSACTION_METHOD: + win8Plugin.commitTransaction(JSON.stringify(args)).done(resultHandler); + break; + case ROLLBACK_TRANSACTION_METHOD: + win8Plugin.rollbackTransaction(JSON.stringify(args)).done(resultHandler); + break; + case ADVANCED_FIND_METHOD: + win8Plugin.advancedFind(JSON.stringify(args)).done(resultHandler); + break; + case FILE_INFO_METHOD: + win8Plugin.fileInfo(JSON.stringify(args)).done(resultHandler); + break; + } + }; + } + + //public API + return { + provision: _provision, + store: _store, + find: _find, + findById: _findById, + advancedFind: _advancedFind, + replace: _replace, + remove: _remove, + allDirty: _allDirty, + pushRequiredCount: _localCount, + count: _count, + isPushRequired: _isDirty, + markpushed: _clean, + removeCollection: _clear, + changePassword: _changePW, + closeAll: _closeDatabase, + destroy: _destroy, + isKeyGenReq: _isKeyGenReq, + startTransaction: _startTransaction, + commitTransaction: _commitTransaction, + rollbackTransaction: _rollbackTransaction, + fileInfo: _fileInfo, + change: _change, + clear: _clearCollection, + sync: _sync + }; + + }(WLJQ)); //WL.db + +} else { + + /** + Provides an async interface to localStorage + @private + **/ + WL.storage = (function(jQuery) { + + 'use strict'; + + var ls = localStorage, + $ = jQuery, + constant = WL.constant, + check = WL.check, + + __genFullName = function(username, name) { + return '' + username + '.' + name; + }, + + __dropFirst = function(username, name, options) { + var deferred = $.Deferred(); + + if (check.isBoolean(options.dropCollection) && options.dropCollection) { + //drop the collection first + return _clearKey(username, name); + } else { + + setTimeout(function() { + deferred.resolve(constant.SUCCESS); + }, 0); + + return deferred.promise(); + } + }, + + __init = function(username, name) { + var deferred = $.Deferred(); + + _read(username, name) + + .then(function(val) { + if (null !== val) { + deferred.resolve(val); + } else { + deferred.resolve(constant.SUCCESS); + } + }) + + .fail(function() { + deferred.reject(constant.PERSISTENT_STORE_FAILURE); + }); + + return deferred.promise(); + }, + + _fileSize = function(usernames, names) { + var sizes = [], + deferred = $.Deferred(), + arr = [], + len = usernames.length; + + for (var x = 0; x < len; x++) { + arr.push(_read(usernames[x], names[x])); + } + + $.when.apply($, arr) + + .done(function() { + for (var x = 0; x < len; x++) { + sizes.push((JSON.stringify(arguments[x]).length * 16) / 8); + } + + deferred.resolve(sizes); + + }) + .fail(function() { + deferred.reject(); + }); + + return deferred.promise(); + }, + + _provision = function(username, name, options) { + var deferred = $.Deferred(); + options = options || {}; + + __dropFirst(username, name, options) + + .then(function() { + return __init(username, name); + }) + + .then(function(data) { + deferred.resolve(data); + }) + + .fail(function(e) { + deferred.reject(e); + }); + + return deferred.promise(); + }, + + _write = function(username, name, data) { + var deferred = $.Deferred(); + + setTimeout(function() { + ls.setItem(__genFullName(username, name), JSON.stringify(data)); + deferred.resolve(constant.SUCCESS); + }, 0); + + return deferred.promise(); + }, + + _read = function(username, name) { + var deferred = $.Deferred(), + ret; + + setTimeout(function() { + ret = JSON.parse(ls.getItem(__genFullName(username, name))); + deferred.resolve(ret); + }, 0); + + return deferred.promise(); + }, + + _readAll = function() { + var deferred = $.Deferred(), + metas = [], + collectionNames = [], + metaKeys = [], + metaIndex, + patt, + colIndex, + cols = [], + col, + ret = []; + + setTimeout(function() { + /*jshint forin:false*/ + for(var metaKey in ls) { + metaIndex = metaKey.indexOf(constant.METADATA_TAG); + if(metaIndex > -1) { + metaKeys.push(metaKey); + } + } + for(var i = 0; i < metaKeys.length; i++) { + collectionNames.push(metaKeys[i].split('.')[0]); + } + + for(var j = 0; j < collectionNames.length; j++) { + for(var key in ls) { + metaIndex = key.indexOf(constant.METADATA_TAG); + colIndex = key.indexOf(collectionNames[j]); + patt = new RegExp('"username":', ['g']); + col = ls.getItem(key); + if(ls.hasOwnProperty(key) && metaIndex === -1 && colIndex > -1 && col.match(patt) !== null) { + cols.push(key.split(constant.PERIOD)[1]); + metas.push(key.split(constant.PERIOD)[0]); + } + } + } + + ret.push({ + metas: metas, + cols: cols + }); + deferred.resolve(ret); + }, 0); + + return deferred.promise(); + }, + + _clearKey = function(username, name) { + var deferred = $.Deferred(); + + setTimeout(function() { + ls.removeItem(__genFullName(username, name)); + deferred.resolve(constant.SUCCESS); + }, 0); + + return deferred.promise(); + }, + + _destroy = function(store) { + var deferred = $.Deferred(), + metaKeys = [], + metaKey, + collectionKey, + validKeys = [], + meta, + usernames = [], + col, + patt, + metadata, + colIndex, + metaIndex; + + setTimeout(function () { + /*jshint forin:false*/ + validKeys = Object.keys(ls); + for(var i = 0; i < validKeys.length; i++) { + metaKey = validKeys[i]; + metaIndex = metaKey.indexOf(constant.METADATA_TAG); + patt = new RegExp('"' + constant.USERNAME + '":', ['g']); + meta = ls.getItem(metaKey); + if(!check.isUndefined(meta) && meta.match(patt) !== null && metaIndex > -1) { + metaKeys.push(metaKey); + metadata = JSON.parse(ls.getItem(metaKey)); + if(check.isString(store)) { + /*jshint maxdepth:4*/ + if(store === metadata.username) { + usernames.push(metadata.username); + ls.removeItem(metaKey); + } + } else { + usernames.push(metadata.username); + ls.removeItem(metaKey); + } + + } + } + for(var j = 0; j < usernames.length; j++) { + for(var k = 0; k < validKeys.length; k++) { + collectionKey = validKeys[k]; + colIndex = collectionKey.indexOf(usernames[j]); + patt = new RegExp('"' + constant.USERNAME + '":' + '"' + usernames[j] + '"', ['g']); + col = ls.getItem(collectionKey); + if(!check.isUndefined(col) && col.match(patt) !== null && colIndex > -1) { + if(ls.getItem(collectionKey) !== null) { + col = JSON.parse(ls.getItem(collectionKey)); + /*jshint maxdepth:5*/ + if(col.username === usernames[j]) { + ls.removeItem(collectionKey); + } + } + } + } + } + deferred.resolve(); + }, 0); + + return deferred.promise(); + }; + + //public api + return { + write: _write, + read: _read, + readAll: _readAll, + destroy: _destroy, + clear: _clearKey, + provision: _provision, + fileSize: _fileSize + }; + + })(WLJQ); //end WL.storage + + /** + JSONStore Indexer for the JS-only implementation + @private + **/ + WL.indexer = (function(underscore) { + + 'use strict'; + + //dependencies + var check = WL.check, + _ = underscore, + + __updatePath = function(oldPath, newItem) { + var retStr; + if (oldPath === '') { + + retStr = '' + newItem.toLowerCase(); + return retStr; + } else { + + retStr = '' + (oldPath + '.' + newItem).toLowerCase(); + return retStr; + } + }, + + __handleJsonObj = function(ret, path, object, searchFields) { + var tmpPath, + key, + i = 0, + l, + idx, + searchFieldEqTempPath = function(sf) { + return sf === tmpPath; + }; + + for (key in object) { + + if (check.isArray(object[key])) { + + for (l = object[key].length; i < l; i++) { + __handleJsonObj(ret, __updatePath(path, key), object[key][i], searchFields); + } + + } else if (check.isObject(object[key])) { + + __handleJsonObj(ret, __updatePath(path, key), object[key], searchFields); + } else { + + tmpPath = __updatePath(path, key); + idx = _.find(searchFields, searchFieldEqTempPath); + + if (!check.isUndefined(idx)) { + //Native stores booleans as 1 (true) and 0 (false) + var isBoolean = false, + intVal = 0; + + /*jshint maxdepth:4*/ + if (check.isBoolean(object[key])) { + + isBoolean = true; + intVal = object[key] ? 1 : 0; + + } + + ret[tmpPath] = ret[tmpPath] || []; + if (isBoolean) { + ret[tmpPath].push(intVal); + } else { + ret[tmpPath].push(object[key]); + } + + } + } + } + }; + + //Given an object from the user, apply the array of search fields. + var _index = function(object, searchFields) { + + var ret = {}; //gets modified by __handleJsonObj + __handleJsonObj(ret, '', object, searchFields); + return ret; + }; + + return { + index: _index + }; + + }(WL_)); //end WL.indexer + + /** + Provides access to our internal storage in the browser + Internally used in WL.JSONStore. + @private + **/ + WL.browser = (function(jQuery, underscore) { + + 'use strict'; + + //dependencies + var constant = WL.constant, + findIterator = WL.findIterator, + storage = WL.storage, + indexer = WL.indexer, + check = WL.check, + $ = jQuery, + _ = underscore; + + var Store = function(options) { + + options = options || {}; + var pw = check.isString(options.pwHash) ? options.pwHash : '', + usr = options.username; + + //holds the set of open collections + this.collections = options.collections || {}; + + this.metadata = { + pwHash: pw, + username: usr + }; + this.storeOpen = false; + + }; + + Store.prototype = { + + saveStoreMetadata: function() { + var that = this, + deferred = $.Deferred(), + name = constant.METADATA_TAG; + + //We don't want the username here, it's a custom thing for the metadata + storage.write(that.metadata.username, name, that.metadata) + + .then(function() { + deferred.resolve(); + }) + + .fail(function() { + deferred.reject(constant.PERSISTENT_STORE_FAILURE); + }); + + return deferred.promise(); + }, + + readStoreMetadata: function(username) { + var that = this, + deferred = $.Deferred(); + + storage.read(username, constant.METADATA_TAG) + + .then(function(data) { + that.metadata = data; + deferred.resolve(); + }) + + .fail(function() { + deferred.reject(constant.PERSISTENT_STORE_FAILURE); + }); + + return deferred.promise(); + }, + + getAllStoreMetadata: function() { + var deferred = $.Deferred(); + + storage.readAll() + + .then(function(data) { + deferred.resolve(data); + }) + .fail(function() { + deferred.reject(constant.PERSISTENT_STORE_FAILURE); + }); + + return deferred.promise(); + }, + + isStoreOpen: function() { + return this.storeOpen; + }, + + addOpened: function(collection) { + this.storeOpen = true; + this.collections[collection.name] = collection; + }, + + removeOpened: function(collectionName) { + + if (_.has(this.collections, collectionName)) { + delete this.collections[collectionName]; + } + }, + + isOpen: function(name) { + return _.has(this.collections, name); + }, + + anyOpen: function() { + + return this.storeOpen && (!_.isEmpty(this.collections)); + }, + + refresh: function(collection) { + var that = this, + deferred = $.Deferred(); + + storage.read(this.metadata.username, collection) + + .then(function(data) { + that.collections[collection] = data; + deferred.resolve(); + }) + + .fail(function() { + deferred.reject(constant.PERSISTENT_STORE_FAILURE); + }); + + return deferred.promise(); + } + }; + + /** + Object that holds all the opened collections in memory + */ + var store = new Store(), + + Collection = function(options) { + options = options || {}; + + this.name = options.name || ''; + this.username = options.username || constant.DEFAULT_USERNAME; + this.searchFields = options.searchFields || {}; + this.additionalSearchFields = options.additionalSearchFields || {}; + this.sfTree = options.sfTree || {}; + this.data = options.data || []; + }, + + Document = function(options) { + options = options || {}; + + this.json = options.json || {}; + this._indexes = options.indexes || {}; + this._deleted = options.deleted || false; + this._dirty = options.dirty || ''; + this._operation = options.operation || ''; + }, + + __createNewStore = function() { + //called from destroy and close to remove all opened collections + store = new Store(); + }, + + __hashPassword = function(pw) { + var bitArray = sjcl.hash.sha256.hash(pw), + pwHash = sjcl.codec.hex.fromBits(bitArray); + + return pwHash; + }, + + __handlePassword = function(pwHash, deferred) { + + //Handle password cases + if (!check.isUndefined(pwHash)) { + if (!check.isUndefined(store.metadata.pwHash)) { + if (pwHash === store.metadata.pwHash) { + //user pw and stored pw match + deferred.resolve(); + } else { + //use pw doesn't match stored pw + deferred.reject(constant.INVALID_KEY_ON_PROVISION); + } + } else { + //Init without pass and then init with pass + deferred.reject(constant.INVALID_KEY_ON_PROVISION); + } + } else { + if (!check.isUndefined(store.metadata.pwHash)) { + //user did not specify password, but we have one saved + deferred.reject(constant.INVALID_KEY_ON_PROVISION); + } + deferred.resolve(); //no passed in or stored passwords + } + }, + + __ensureStoreOpen = function(pwHash, user) { + //Make sure the store has been opened. + var deferred = $.Deferred(); + + if (!store.isStoreOpen()) { + + store.readStoreMetadata(user) + + .then(function() { + + if (check.isUndefined(store.metadata)) { + + //Very first provision, need to store the key and username + store.metadata = { + pwHash: pwHash, + username: user + }; + + deferred.resolve(); + + } else { //Not the first provision + + __handlePassword(pwHash, deferred); + } + }) + + .fail(function(e) { + deferred.reject(e); + }); + + } else if (!check.isUndefined(store.transactionState)) { + deferred.reject(constant.TRANSACTION_FAILURE_DURING_INIT); + } else { + setTimeout(function() { + if (user === store.metadata.username) { + deferred.resolve(); + } else { + deferred.reject(constant.USERNAME_MISMATCH_DETECTED); + } + }, 0); + } + + return deferred.promise(); + }, + + __searchFieldsMatch = function(newSearchFields, collection) { + return _.isEqual(store.collections[collection].searchFields, newSearchFields); + }, + + __processAdditionalSearchFields = function(searchFields, options) { + var asfNames = [], + sfKeys; + + if (!_.isEmpty(options.additionalSearchFields)) { + sfKeys = _.keys(searchFields); //e.g. [fn, ln] + sfKeys.unshift(options.additionalSearchFields); //e.g. [{orderid: 'number'}, fn, ln] + //Doing this because apply takes idx 0 as the first argument + asfNames = _.omit.apply(this, sfKeys); //omit searchFields keys from additional SF + } + + return asfNames; + }, + + __provisionPreprocessor = function(options) { + var deferred = $.Deferred(), + pwHash; + + if (check.isString(options.collectionPassword) && !_.isEmpty(options.collectionPassword)) { + pwHash = __hashPassword(options.collectionPassword); + } + + //FIPS is not supported in the browser environment + if (options.fipsEnabled) { + setTimeout(function() { + return deferred.reject(constant.FIPS_ENABLEMENT_FAILURE); + }, 0); + } + + __ensureStoreOpen(pwHash, options.username) + + .then(function() { + + if (check.isUndefined(pwHash)) { + //no password, could be a 2nd init attempt where we have cleared the pw. + return deferred.resolve(constant.SUCCESS); + + } else { + + if (pwHash !== store.metadata.pwHash) { + //user password didn't match stored password. + return deferred.reject(constant.INVALID_KEY_ON_PROVISION); + + } else { + + return deferred.resolve(constant.SUCCESS); + } + } + }) + + .fail(function(e) { + return deferred.reject(e); + }); + + return deferred.promise(); + }, + + __createNewCollection = function(collection, searchFields, options) { + var asfNames = __processAdditionalSearchFields(searchFields, options), + c = new Collection({ + name: collection, + searchFields: searchFields, + additionalSearchFields: asfNames, + username: options.username, + data: [] + }); + + store.addOpened(c); + return c; + }, + + __handleStorageProvisionResult = function(result, collection, searchFields, options) { + //rc === 0 means the collection doesn't exist, so create it + //rc === { data.. } means it does exist, open it up + var c; + + if (result === 0) { + c = __createNewCollection(collection, searchFields, options); + return storage.write(options.username, collection, c); + + } else { + store.addOpened(result); + + if (!__searchFieldsMatch(searchFields, collection)) { + store.removeOpened(result.name); + return $.Deferred().reject(constant.PROVISION_TABLE_SEARCH_FIELDS_MISMATCH); + } + + //Open existing collection, backwards compatible with initCollection + return $.Deferred().resolve(1); + } + }, + + _provision = function(collection, searchFields, options) { + var userRc; + + __provisionPreprocessor(options) + + .then(function() { + return storage.provision(options.username, collection, options); + }) + + .then(function(rc) { + return __handleStorageProvisionResult(rc, collection, searchFields, options); + }) + + .then(function(rc) { + userRc = rc; //0 or 1 is returned to the user + return store.saveStoreMetadata(); + }) + + .then(function() { + options.onSuccess(userRc); + }) + + .fail(function(e) { + options.onFailure(e); + }); + }, + + __storeImpl = function(collection, data, options, rc) { + var indexes, + sfKeys = [], + operation = options.isAdd ? 'add' : '', + dirty = options.isAdd ? new Date() : 0, + deferred = $.Deferred(); + + sfKeys = _.keys(store.collections[collection].searchFields); + + _.each(data, function(obj) { + indexes = indexer.index(obj, sfKeys); + if (!check.isUndefined(options.additionalSearchFields)) { + //add each additionalSearchField to the indexes + _.each(options.additionalSearchFields, function(val, key) { + key = key.toLowerCase(); + if (!_.isArray(indexes[key])) { + indexes[key] = []; + } + indexes[key].push(val); + }); + } + var d = new Document({ + json: obj, + indexes: indexes, + dirty: dirty, + operation: operation + }); + d._id = store.collections[collection].data.push(d); + + }); + rc = data.length; + + storage.write(store.metadata.username, collection, store.collections[collection]) + + .then(function() { + deferred.resolve(rc); + }) + + .fail(function(e) { + deferred.reject(e); + }); + + return deferred.promise(); + }, + + _store = function(collection, data, options) { + if (!store.isOpen(collection)) { + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + return __storeImpl(collection, data, options); + }) + .then(function(res) { + options.onSuccess(res); + }) + .fail(function(e) { + options.onFailure(e); + }); + } + }, + + __lowerCaseKeys = function(obj) { + var key; + + _.each(obj, function(value, k) { + key = k.toLowerCase(); + /*jshint eqeqeq:false*/ + if (key != k) { + obj[key] = obj[k]; + delete obj[k]; + } + }); + + return obj; + }, + + __handleOptionsAndDuplicates = function(options) { + if (!check.isUndefined(options.limit)) { + if (!check.isUndefined(options.offset)) { + //We have a limit and offset + if (options.counters.skippedCtr < options.offset) { + options.counters.skippedCtr = options.counters.skippedCtr + 1; + return false; //offset not reached, so skip this match + } + } + + if (options.counters.foundCtr < options.limit) { + options.counters.foundCtr = options.counters.foundCtr + 1; + if (check.isUndefined(options.dataTracker[options.doc[constant.ID_KEY]])) { + //we haven't seen this element + options.dataTracker[options.doc[constant.ID_KEY]] = true; + options.interceptor(options.doc); + return true; //all keys matched and we are under our limit + } else { + //We have a match, but it was already captured so skip it. + return false; + } + } + //We are over our limit + return false; + } + + //Ensure we don't return duplicates + if (check.isUndefined(options.dataTracker[options.doc[constant.ID_KEY]])) { + //Element not seen before + options.dataTracker[options.doc[constant.ID_KEY]] = true; + options.interceptor(options.doc); + return true; //all keys matched and we are under our limit + } else { + //We have a match, but it was already captured so skip it. + return false; + } + }, + + __queryProcessorImpl = function(doc) { + + var kLower = '', + dataTracker = this.dataTracker, //records indexes already found + counters = this.counters, + offset = this.offset, + limit = this.limit, + exact = this.exact, + query = this.query, + interceptor = this.interceptor, + sf = this.sf, //searchFields + ptr, + searchStr, + mySearchStr, + found, + hasOwn = Object.prototype.hasOwnProperty, + likeIterator = function(idx) { + + idx = ('' + idx).toLowerCase(); + this.searchStr = this.searchStr.toLowerCase(); + return idx.indexOf(this.searchStr) > -1; + }, + exactIterator = function(idx) { + + //Not lowercasing anymore to match native behavior; in native, when comparing with exact=true it is case sensitive + idx = ('' + idx); + + if (this.sf[this.kLower] === 'integer') { + return Number(idx) === Number(this.searchStr); + + } else { + return idx === this.searchStr; + } + + }, + iteratorImpl = exact ? exactIterator : likeIterator, + options = { + limit: limit, + offset: offset, + interceptor: interceptor, + dataTracker: dataTracker, + counters: counters, + doc: doc + }; + + //doc is a document in the collection + if (!check.isObject(doc)) { + //has been removed + return false; + } + + if (doc._deleted) { + return false; //skip this doc, it's been marked deleted and markDirty flag is set + } + + //Loop over every key in the query obj and look for matches + for (var k in query) { + + if (hasOwn.call(query, k)) { + + kLower = k.toLowerCase(); + //check for _id field + if (kLower === constant.ID_KEY) { + + /*jshint maxdepth:4*/ + if (doc._id === query[k]) { + //_id fields are the same + continue; + } else { + return false; //_id did not match + } + + } else if(kLower === constant.JSON_DATA_KEY) { + continue; + } else { + ptr = doc._indexes; + } + + //not an _id field, looking at indexes + if (!check.isUndefined(ptr[kLower])) { + + //Simulate LIKE query search + searchStr = query[k]; + + //In native booleans are stored as 1 (true) or 0 (false) + if (check.isBoolean(searchStr)) { + mySearchStr = searchStr ? '1' : '0'; + } else { + //Not lowercasing anymore to match native behavior; in native, when comparing with exact=true it is case sensitive + mySearchStr = ('' + searchStr); + } + + found = _.find(ptr[kLower], iteratorImpl, { + searchStr: mySearchStr, + sf: sf, + kLower: kLower + }); + + if (!check.isUndefined(found)) { + continue; // We found a matching key, make sure the rest match + } else { + return false; + } + + } else { + //indexes did not contain the search key + return false; + } + } + } + + return __handleOptionsAndDuplicates(options); + }, + + __queryProcessorAdvImpl = function(doc) { + + var kLower = '', + dataTracker = this.dataTracker, //records indexes already found + counters = this.counters, + offset = this.offset, + limit = this.limit, + query = this.query, + interceptor = this.interceptor, + sf = this.sf, //searchFields + ptr, + searchStr, + mySearchStr, + found, + hasOwn = Object.prototype.hasOwnProperty, + iteratorImpl, + options = { + limit: limit, + offset: offset, + interceptor: interceptor, + dataTracker: dataTracker, + counters: counters, + doc: doc + }; + + //doc is a document in the collection + if (!check.isObject(doc)) { + //has been removed + return false; + } + + if (doc._deleted) { + return false; //skip this doc, it's been marked deleted + } + + //Loop over every key in the query obj and look for matches + for (var k in query) { + if (hasOwn.call(query, k)) { + kLower = k.replace(constant.QUERYPART, '').toLowerCase(); + if (kLower === constant.ID_KEY) { + /*jshint maxdepth:4*/ + if (doc._id !== query[k]) { + return false; + } + } else { + ptr = doc._indexes; + } + + searchStr = query[k]; + if (check.isBoolean(searchStr)) { + mySearchStr = searchStr ? '1' : '0'; + } else { + mySearchStr = searchStr; + } + + iteratorImpl = findIterator.get(kLower); + + /*jshint forin:false*/ + for (var j in mySearchStr) { + mySearchStr[j] = __lowerCaseKeys(mySearchStr[j]); + found = _.find(ptr, iteratorImpl, { + searchStr: mySearchStr[j], + sf: sf, + kLower: kLower + }); + if (check.isUndefined(found)) { + return false; + } + } + + } + } + + return __handleOptionsAndDuplicates(options); + }, + + //internal find impl, does all the looping and filtering + __queryProcessor = function(ctx) { + var retData = [], + currData = [], + dataTracker = [], //records indexes already found + counters = { + foundCtr: 0, + skippedCtr: 0 + }, + + //no default for limit or offset, since you can specify a negative limit, + //and limit = 0. We have to deal with undefined as the case where no limit + //was specified so we skip + interceptor = ctx.interceptor || function() {}; + + _.each(ctx.filter, function(value, index) { + ctx.filter[index] = value.toLowerCase(); + }); + + _.each(ctx.query, function(query) { + + //loop over all the docs if there is a hit, return _id and json + ctx.dataTracker = dataTracker; + ctx.counters = counters; + ctx.interceptor = interceptor; + ctx.query = query; + + currData = check.filterPick(ctx.data, [constant.ID_KEY, constant.JSON_DATA_KEY, constant.INDEXES_KEY, ctx.filter], __queryProcessorImpl, ctx); + + retData = retData.concat(currData); + + }); + return retData; + }, + + //internal find impl, does all the looping and filtering + __queryProcessorAdv = function(ctx) { + var retData = [], + currData = [], + dataTracker = [], //records indexes already found + counters = { + foundCtr: 0, + skippedCtr: 0 + }, + + //no default for limit or offset, since you can specify a negative limit, + //and limit = 0. We have to deal with undefined as the case where no limit + //was specified so we skip + interceptor = ctx.interceptor || function() {}; + + _.each(ctx.filter, function(value, index) { + ctx.filter[index] = value.toLowerCase(); + }); + + _.each(ctx.query, function(query) { + + //loop over all the docs if there is a hit, return _id and json + ctx.dataTracker = dataTracker; + ctx.counters = counters; + ctx.interceptor = interceptor; + ctx.query = query; + + currData = check.filterPick(ctx.data, [constant.ID_KEY, constant.JSON_DATA_KEY, constant.INDEXES_KEY, ctx.filter], __queryProcessorAdvImpl, ctx); + + retData = retData.concat(currData); + + }); + return retData; + }, + + _findImpl = function(collection, query, options) { + + var col = store.collections[collection], + reversedData = [], + retData = [], + clonesReturn, + deferred = $.Deferred(), + clones = [], + l, + ctx = { + query: query, + data: col.data, + sf: col.searchFields, + limit: options.limit, + offset: options.offset, + exact: options.exact, + filter: options.filter, + interceptor: function() {} + }; + + if (check.isInt(ctx.limit) && (ctx.limit < 0)) { + //We have a negative limit, reverse the array before traversing + l = ctx.data.length; + + while (l--) { + reversedData.push(ctx.data[l]); + } + + ctx.data = reversedData; + ctx.limit = Math.abs(ctx.limit); + } + + /* + * If the results are to be sorted, disable limit and size to get the unsorted array first before sorting + */ + if (!_.isUndefined(options.sort)) { + delete ctx.limit; + delete ctx.offset; + } + + retData = __queryProcessor(ctx); + + clones = _.cloneDeep(retData); //31% faster than $.extend(true, [], retData) + + clonesReturn = _sort(clones, options, deferred); + + options.onSuccess(clonesReturn); + + }, + + /* + * Javascript sort impl + */ + _sort = function(clones, options) { + if (!_.isUndefined(options.sort)) { + clones.sort(function(a, b) { + for (var i = 0; i < options.sort.length; i++) { + var propertyKey = _.keys(options.sort[i])[0]; + if (options.sort[i][propertyKey].toLowerCase() === constant.ASCENDING) { + if (!check.isUndefined(a.json) && !check.isUndefined(b.json) && (a.json[propertyKey] > b.json[propertyKey])) { + return 1; + } else if (a[propertyKey] > b[propertyKey]) { + return 1; + } else if (!check.isUndefined(a.json) && !check.isUndefined(b.json) && (a.json[propertyKey] < b.json[propertyKey])) { + return -1; + } else if (a[propertyKey] < b[propertyKey]) { + return -1; + } else if (i === (options.sort.length - 1)) { + return 0; + } + } + if (options.sort[i][propertyKey].toLowerCase() === constant.DESCENDING) { + if (!check.isUndefined(a.json) && typeof!check.isUndefined(b.json) && (a.json[propertyKey] < b.json[propertyKey])) { + return 1; + } else if (a[propertyKey] < b[propertyKey]) { + return 1; + } else if (!check.isUndefined(a.json) && typeof!check.isUndefined(b.json) && (a.json[propertyKey] > b.json[propertyKey])) { + return -1; + } else if (a[propertyKey] > b[propertyKey]) { + return -1; + } else if (i === (options.sort.length - 1)) { + return 0; + } + } + } + }); + } + + /* + * After sort, apply limit and offset + */ + + var limit, offset, clonesReturn = [], + i, count; + if (!_.isUndefined(options.sort)) { + if (!_.isUndefined(options.offset)) { + offset = options.offset; + } + if (!_.isUndefined(options.limit)) { + limit = Math.abs(options.limit); + } + + if (!_.isUndefined(limit) && !_.isUndefined(offset)) { + for (i = options.offset, count = 0; i < clones.length && count < limit; i++, count++) { + clonesReturn.push(clones[i]); + } + } else if (!_.isUndefined(limit) && _.isUndefined(offset)) { + for (i = 0, count = 0; i < clones.length && count < limit; i++, count++) { + clonesReturn.push(clones[i]); + } + } else if (_.isUndefined(limit) && !_.isUndefined(offset)) { + for (i = offset; i < clones.length; i++) { + clonesReturn.push(clones[i]); + } + } else { + for (i = 0; i < clones.length; i++) { + clonesReturn.push(clones[i]); + } + } + } else { + clonesReturn = clones; + } + + return clonesReturn; + //options.onSuccess(clonesReturn); + }, + + _find = function(collection, query, options) { + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + _findImpl(collection, query, options); + }) + .fail(function(e) { + options.onFailure(e); + }); + } + + }, //end find + + __findByIdImpl = function(collection, id, options) { + var data = store.collections[collection].data, + retData = [], + clones = []; + + //Loop over all the documents in the store + _.each(data, function(doc) { + //See if this doc matches an id in our query + if (!check.isUndefined(doc) && _.contains(id, doc._id)) { + if (doc[constant.DELETED_KEY] === false) { + //make sure document is not marked deleted + retData.push(_.pick(doc, constant.ID_KEY, constant.JSON_DATA_KEY)); + } + } + }); + + clones = _.cloneDeep(retData); //31% faster than $.extend(true, [], retData) + + options.onSuccess(clones); + }, + + _findById = function(collection, id, options) { + + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + __findByIdImpl(collection, id, options); + }) + + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + } + }, + + __advancedFindImpl = function(collection, query, options) { + var col = store.collections[collection], + reversedData = [], + clonesReturned, + retData = [], + clones = [], + l, + ctx = { + query: query, + data: col.data, + sf: col.searchFields, + limit: options.limit, + offset: options.offset, + exact: options.exact, + filter: options.filter, + interceptor: function() {} + }; + + if (check.isInt(ctx.limit) && (ctx.limit < 0)) { + //We have a negative limit, reverse the array before traversing + l = ctx.data.length; + + while (l--) { + reversedData.push(ctx.data[l]); + } + + ctx.data = reversedData; + ctx.limit = Math.abs(ctx.limit); + } + + /* + * If the results are to be sorted, disable limit and size to get the unsorted array first before sorting + */ + if (!_.isUndefined(options.sort)) { + delete ctx.limit; + delete ctx.offset; + } + + retData = __queryProcessorAdv(ctx); + + clones = _.cloneDeep(retData); //31% faster than $.extend(true, [], retData) + + clonesReturned = _sort(clones, options); + options.onSuccess(clonesReturned); + //deferred.resolve(clonesReturned); + //return deferred.promise(); + + }, + + _advancedFind = function(collection, query, options) { + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + __advancedFindImpl(collection, query, options); + }) + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + } + }, + + __replaceImpl = function(collection, docs, options) { + + var col = store.collections[collection], + data = col.data, + found = [], + operation = options.isRefresh ? '' : 'replace', + dirty = options.isRefresh ? 0 : new Date(), + sfKeys, + failedDoc; + + //if attempting to replace with an empty array just return 0 + if (check.isEmptyArray(docs)) { + return options.onSuccess(0); + } + + //See if the documents exist in the store, if we can't find one, track it and pass it to the fail cb + _.each(docs, function(doc) { + var idx = doc._id - 1, + curr = data[idx]; + + if (check.isUndefined(curr) || curr[constant.DELETED_KEY]) { + //error, don't update anything, save the bad doc to return to the user + failedDoc = doc; + + } else { + found.push(curr); + } + }); + + if (!check.isUndefined(failedDoc)) { + return options.onFailure([failedDoc]); + } + + if (found.length !== docs.length) { + return options.onFailure(constant.PERSISTENT_STORE_FAILURE); + } + + //We found all the docs to be replaced, now process them + sfKeys = _.keys(col.searchFields); + + _.each(docs, function(doc) { + + var docData = doc.json, + indexes = indexer.index(docData, sfKeys), + docId = doc._id - 1, + newDoc = new Document({ + json: docData, + indexes: indexes, + dirty: dirty, + //if the operation was already add, keep it + operation: (data[docId]._operation === 'add') ? 'add' : operation + }); + + newDoc._id = doc._id; + + //preserve the old additional indexes + _.each(_.keys(col.additionalSearchFields), function(key) { + newDoc._indexes[key] = data[docId]._indexes[key]; + }); + + data[docId] = newDoc; //swap with the newly indexed item. + }); + + storage.write(store.metadata.username, collection, col) + + .then(function() { + options.onSuccess(found.length); + }) + + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + }, + + _replace = function(collection, docs, options) { + + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + store.refresh(collection) + + .then(function() { + __replaceImpl(collection, docs, options); + }) + + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + } + }, + + __removeImpl = function(collection, query, options) { + var col = store.collections[collection], + retData, + addIds = [], + operation = options.isErase ? '' : 'remove', + interceptor = function(doc) { + + if ((doc._operation === 'add' && operation === 'remove' && !check.isNumber(doc._dirty)) || operation === '') { + addIds.push(doc._id); + } + + doc._deleted = true; + doc._dirty = new Date(); + doc._operation = operation; + }, + ctx = { + data: col.data, + sf: col.searchFields, + query: query, + interceptor: interceptor, + exact: options.exact + }; + + retData = __queryProcessor(ctx); + + //Items that were operation 'add' are local only so a remove, erases them + _.each(addIds, function(id) { + delete ctx.data[id - 1]; + }); + + storage.write(store.metadata.username, collection, col) + + .then(function() { + options.onSuccess(retData.length); + }) + + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + }, + + _remove = function(collection, query, options) { + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + __removeImpl(collection, query, options); + }) + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + } + }, + + __allDirtyImpl = function(collection, docs, options) { + + var data = store.collections[collection].data, + result = [], + found = [], + idx = 0, + curr; + + if (docs.length > 0) { + + _.each(docs, function(doc) { + idx = doc._id - 1; + curr = data[idx]; + if ((!check.isUndefined(curr)) && (curr[constant.DIRTY_KEY].length > 0)) { + found.push(curr); + } + }); + + options.onSuccess(found); + + } else { + + result = _.filter(data, function(doc) { + return (!check.isUndefined(doc)) && (doc[constant.DIRTY_KEY].length > 0); + }); + + _.each(result, function(doc, index) { + result[index] = _.pick(doc, [constant.JSON_DATA_KEY, constant.ID_KEY, constant.OPERATION_KEY, constant.DIRTY_KEY]); + }); + + options.onSuccess(result); + } + }, + + _allDirty = function(collection, docs, options) { + + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + __allDirtyImpl(collection, docs, options); + }) + + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + + } + }, + + __cleanImpl = function(collection, docs, deferred) { + var col = store.collections[collection], + data = col.data, + numOfCleans = 0, + doc, + failedDoc = {}, + docId; + + if (docs.length === 0) { + setTimeout(function() { + deferred.resolve(0); + }); + } + + _.each(docs, function(minimalDoc) { + //minimalDoc contains only _id and operation keys + + docId = minimalDoc._id - 1; + doc = data[docId]; + + if (!check.isUndefined(doc)) { + numOfCleans++; + if (doc[constant.OPERATION_KEY] === constant.OPERATION_DELETED) { + //do the actual delete here. + data[docId] = null; + } else { + doc[constant.DIRTY_KEY] = ''; + doc[constant.DELETED_KEY] = false; + doc[constant.OPERATION_KEY] = ''; + } + + } else { + failedDoc = doc; + } + }); + + storage.write(store.metadata.username, collection, col) + + .then(function() { + deferred.resolve(numOfCleans); + }) + + .fail(function() { + deferred.reject(failedDoc); + }); + }, + + _clean = function(collection, docs) { + var deferred = $.Deferred(); + + if (!store.isOpen(collection)) { + + setTimeout(function() { + deferred.reject(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + __cleanImpl(collection, docs, deferred); + }) + + .fail(function() { + deferred.reject(constant.PERSISTENT_STORE_FAILURE); + }); + } + return deferred.promise(); + + }, + + __localCountImpl = function(collection, options) { + var data = store.collections[collection].data, + count = 0, + memo = 0, + iterator = function(memo, doc) { + if (check.isUndefined(doc) || doc[constant.DIRTY_KEY].length === 0) { + return memo; + } else { + return memo + 1; + } + }; + + count = _.reduce(data, iterator, memo); + + options.onSuccess(count); + }, + + _localCount = function(collection, options) { + + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + __localCountImpl(collection, options); + }) + + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + } + }, + + __countWholeCollectionImpl = function(collection, options) { + + var data = store.collections[collection].data, + count = 0, + memo = 0, + iterator = function(memo, doc) { + if (check.isUndefined(doc) || doc[constant.DELETED_KEY]) { + return memo; + } else { + return memo + 1; + } + }; + + count = _.reduce(data, iterator, memo); + options.onSuccess(count); + }, + + __countWithQuery = function(collection, options, query) { + + var internalSuccessCallback = function(res) { + //We only need to return the length of the results, not the actual results + options.onSuccess(res.length); + }, + + ops = _.defaults({ + onSuccess: internalSuccessCallback + }, options); + + _findImpl(collection, [query], ops); + + }, + + _count = function(collection, options, query) { + + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + if (check.isSimpleObject(query)) { + + __countWithQuery(collection, options, query); + + } else { + + __countWholeCollectionImpl(collection, options); + } + + }) + + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + } + + }, + + __isDirtyImpl = function(collection, id, options) { + var data = store.collections[collection].data, + docId = id - 1, + rc = 0; + + if (!check.isUndefined(data[docId])) { + rc = data[docId][constant.DIRTY_KEY].length > 0 ? 1 : 0; + options.onSuccess(rc); + } + }, + + _isDirty = function(collection, docId, options) { + + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + store.refresh(collection) + + .then(function() { + __isDirtyImpl(collection, docId._id, options); + }) + + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + } + }, + + __clearImpl = function(collection, options) { + storage.clear(store.metadata.username, collection) + + .then(function() { + store.removeOpened(collection); + options.onSuccess(constant.SUCCESS); + }) + + .fail(function() { + options.onFailure(constant.ERROR_CLEARING_COLLECTION); + }); + }, + + _clear = function(collection, options) { + + if (!store.isOpen(collection)) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else if (!check.isUndefined(store.transactionState)) { + setTimeout(function() { + options.onFailure(constant.TRANSACTION_FAILURE_DURING_REMOVE_COLLECTION); + }, 0); + } else { + + __clearImpl(collection, options); + } + }, + + __changePWImpl = function(oldPw, newPw, username, options) { + + var oldHash = __hashPassword(oldPw), + newHash = __hashPassword(newPw); + + if (store.metadata.username === username && oldHash === store.metadata.pwHash) { + + store.metadata.pwHash = newHash; + + store.saveStoreMetadata() + + .then(function() { + options.onSuccess(constant.SUCCESS); + }) + + .fail(function() { + options.onFailure(constant.PERSISTENT_STORE_FAILURE); + }); + + } else { //bad old password or wrong username + + setTimeout(function() { + options.onFailure(constant.ERROR_CHANGING_PASSWORD); + }, 0); + } + }, + + _changePW = function(oldPw, newPw, username, options) { + + if (!store.anyOpen()) { + + setTimeout(function() { + options.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + + } else { + + __changePWImpl(oldPw, newPw, username, options); + } + + //Clear passwords from memory + oldPw = null; + newPw = null; + }, + + _closeDatabase = function(options) { + + if (!check.isUndefined(store.transactionState)) { + return options.onFailure(constant.TRANSACTION_FAILURE_DURING_CLOSE_ALL); + } + __createNewStore(); + + setTimeout(function() { + options.onSuccess(constant.SUCCESS); + }, 0); + + }, + + _destroy = function(options, username) { + + if (!check.isUndefined(store.transactionState) && + (check.isObject(store.transactionState) && _.keys(store.transactionState).length > 0)) { + return options.onFailure(constant.TRANSACTION_FAILURE_DURING_DESTROY); + } + + __createNewStore(); + + storage.destroy(username) + + .then(function() { + options.onSuccess(constant.SUCCESS); + }) + + .fail(function(e) { + options.onFailure(e); + }); + }, + + _isKeyGenReq = function(username, callback) { + + //JavaScript-only implementation does not provide data encryption / security features + setTimeout(function() { + callback(constant.KEYGEN_NOT_REQUIERED); + }, 0); + }, + + _startTransaction = function(callbacks) { + if (!store.isStoreOpen()) { + setTimeout(function() { + delete store.transactionState; + return callbacks.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + } + if (check.isUndefined(store.transactionState)) { + setTimeout(function() { + store.transactionState = _.cloneDeep(store.collections); + return callbacks.onSuccess(0); + }, 0); + } else { + setTimeout(function() { + return callbacks.onFailure(constant.TRANSACTION_IN_PROGRESS); + }, 0); + } + + }, + + _commitTransaction = function(callbacks) { + if (!store.isStoreOpen()) { + setTimeout(function() { + return callbacks.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + } + if (check.isUndefined(store.transactionState)) { + setTimeout(function() { + return callbacks.onFailure(constant.NO_TRANSACTION_IN_PROGRESS); + }, 0); + } else { + setTimeout(function() { + delete store.transactionState; + callbacks.onSuccess(0); + }, 0); + } + + }, + + _rollbackTransaction = function(callbacks) { + var keys = _.keys(store.collections); + var writes = []; + if (!store.isStoreOpen()) { + setTimeout(function() { + callbacks.onFailure(constant.PERSISTENT_STORE_NOT_OPEN); + }, 0); + } + + if (check.isUndefined(store.transactionState)) { + setTimeout(function() { + callbacks.onFailure(constant.NO_TRANSACTION_IN_PROGRESS); + }, 0); + } else { + store.collections = _.cloneDeep(store.transactionState); + delete store.transactionState; + + _.each(keys, function(value) { + writes.push(storage.write(store.metadata.username, value, store.collections[value])); + }); + $.when.apply(this, writes) + .then(function() { + callbacks.onSuccess(0); + }) + .fail(function() { + callbacks.onFailure(constant.TRANSACTION_FAILURE_DURING_ROLLBACK); + }); + + } + }, + + _change = function(collection, data, options) { + var col = store.collections[collection], + newJSONData = [], + numOfReplaces = 0, + newData = [], + foundPickedData = [], + newPickedData = [], + foundJSONData = [], + retData = [], + replaceData = [], + ctx = { + data: col.data, + sf: col.searchFields, + addNew: options.addNew, + markDirty: options.markDirty, + replaceCriteria: options.replaceCriteria + }; + + if (data.length === 0) { + setTimeout(function() { + options.onSuccess(0); + }, 0); + } else if(check.isUndefined(ctx.replaceCriteria) && !ctx.addNew){ + setTimeout(function() { + options.onSuccess(0); + }, 0); + }else { + _.each(data, function(value, index) { + newJSONData[index] = { + 'json': value + }; + retData.push(_.pick(value, ctx.replaceCriteria)); + }); + WL.JSONStore.get(col.name).find(retData, { + exact: true + }) + .then(function(res) { + if (res.length > 0 && !check.isUndefined(ctx.replaceCriteria)) { + foundJSONData = _.cloneDeep(res); + if(foundJSONData.length > newJSONData.length) { + for(var i = 0; i < foundJSONData.length; i++) { + /*jshint maxdepth:4*/ + for(var j = 0; j < newJSONData.length; j++) { + newPickedData = _.pick(newJSONData[j].json, ctx.replaceCriteria); + foundPickedData = _.pick(foundJSONData[i].json, ctx.replaceCriteria); + /*jshint maxdepth:5*/ + if(_.isEqual(newPickedData, foundPickedData)) { + replaceData.push( _.merge(foundJSONData[i], newJSONData[j])); + } + } + } + } else { + replaceData = _.initial(_.merge(foundJSONData, newJSONData), data.length - res.length); + } + return WL.JSONStore.get(col.name).replace(replaceData, { + markDirty: ctx.markDirty + }); + } else { + + var deferred = $.Deferred(); + setTimeout(function() { + deferred.resolve(0); + }, 0); + return deferred.promise(); + } + + }) + .then(function(res) { + numOfReplaces = res; + if (ctx.addNew) { + newData = _.last(data, data.length - res); + return WL.JSONStore.get(col.name).add(newData, { + markDirty: ctx.markDirty + }); + } else { + var deferred = $.Deferred(); + setTimeout(function() { + deferred.resolve(0); + }, 0); + return deferred.promise(); + } + }) + .then(function(res) { + options.onSuccess(res + numOfReplaces); + }) + .fail(function(e) { + options.onFailure(e.err); + }); + } + }, + + _clearCollection = function(collection, options) { + if (check.isUndefined(store.collections[collection])) { + setTimeout(function() { + options.onFailure(constant.ERROR_CLEARING_COLLECTION); + }, 0); + } else { + + //delete local documents and from delete + + store.collections[collection].data = []; + + storage.write(store.metadata.username, collection, store.collections[collection]) + + .then(function(res) { + options.onSuccess(res); + + }) + .fail(function() { + options.onFailure(constant.ERROR_CLEARING_COLLECTION); + }); + + } + + }, + + _fileInfo = function(callbacks) { + var fileInfo = [], + usernames = [], + cols = [], + name = [], + len, + x = 0, + isEncrypted = []; + + if (!store.isStoreOpen()) { + callbacks.onSuccess(fileInfo); + } else { + + store.getAllStoreMetadata() + .then(function(res) { + len = res[0].metas.length; + while (x < len) { + usernames.push(res[0].metas[x]); + cols.push(res[0].cols[x]); + name.push(res[0].metas[x]); + // there is no data encryption in JS implementation + isEncrypted.push(false); + x++; + } + return storage.fileSize(usernames, cols); + }) + + .then(function(size) { + x = 0; + while (x < len) { + fileInfo.push({ + username: usernames[x], + size: size[x], + name: name[x], + isEncrypted: isEncrypted[x] + }); + x++; + } + callbacks.onSuccess(fileInfo); + }) + .fail(function() { + callbacks.onFailure(constant.JSON_STORE_FILE_INFO_ERROR); + }); + + } + + }; + + //public API + return { + provision: _provision, + store: _store, + find: _find, + advancedFind: _advancedFind, + findById: _findById, + replace: _replace, + remove: _remove, + allDirty: _allDirty, + pushRequiredCount: _localCount, + count: _count, + isPushRequired: _isDirty, + markpushed: _clean, + removeCollection: _clear, + changePassword: _changePW, + closeAll: _closeDatabase, + destroy: _destroy, + isKeyGenReq: _isKeyGenReq, + startTransaction: _startTransaction, + commitTransaction: _commitTransaction, + rollbackTransaction: _rollbackTransaction, + fileInfo: _fileInfo, + change: _change, + clear: _clearCollection + }; + + }(WLJQ, WL_)); //WL.browser +} + +/** +Provides the API for storing JSON data locally, it may be linked to an adapter for data syncronization. +**/ +WL._JSONStoreImpl = (function(jQuery, underscore) { + + 'use strict'; + + /** + Dependencies + @private + */ + var db = WL.db || WL.browser, + _ = underscore, + check = WL.check, + cb = WL.callback, + constant = WL.constant, + jspath = WL.jspath, + $ = jQuery, + ErrorObject = cb.ErrorObject, + /** + CONSTANTS / GLOBALS + @private + */ + COLLECTIONS = {}, + EVENT_LABELS = { + success: 'WL/JSONSTORE/SUCCESS', + failure: 'WL/JSONSTORE/FAILURE' + }, + ERROR = constant.ERROR, + PWD, + + advanceFindQueryOperations = { + like: constant.LIKE, + notLike: constant.NOTLIKE, + rightLike: constant.RIGHTLIKE, + notRightLike: constant.NOTRIGHTLIKE, + leftLike: constant.LEFTLIKE, + notLeftLike: constant.NOTLEFTLIKE, + equal: constant.EQUAL, + notEqual: constant.NOTEQUAL, + lessThan: constant.LESSTHAN, + lessOrEqualThan: constant.LESSOREQUALTHAN, + greaterThan: constant.GREATERTHAN, + greaterOrEqualThan: constant.GREATEROREQUALTHAN, + between: constant.BETWEEN, + notBetween: constant.NOTBETWEEN, + inside: constant.INSIDE, + notInside: constant.NOTINSIDE + }, + + /** + Interacts with a single collection. + @private + @param name {string} Collection name. + @param searchFields {searchFields} + @param [adapter] {Adapter} + */ + /*jshint maxparams:6*/ + /* jshint shadow:true */ + JSONStoreInstance = function(name, searchFields, adapter, additionalSearchFields, username, deferred) { + + this.name = name || ''; + this.username = username || constant.DEFAULT_USERNAME; + this.searchFields = searchFields || {}; + this.adapter = adapter; //no default value for backwards compatibility + this.additionalSearchFields = additionalSearchFields || {}; + this.promise = deferred.promise(); + + }, + + /** + Interacts with a single collection. + @private + @param name {string} Collection name. + @param searchFields {searchFields} + @param [adapter] {Adapter} + */ + /* jshint shadow:true */ + /*jshint maxparams:8*/ + JSONStoreInstance = function(name, searchFields, adapter, additionalSearchFields, username, deferred, onSyncSuccess, onSyncFailure){ + + this.name = name || ''; + this.username = username || constant.DEFAULT_USERNAME; + this.searchFields = searchFields || {}; + this.adapter = adapter; //no default value for backwards compatibility + this.additionalSearchFields = additionalSearchFields || {}; + this.promise = deferred.promise(); + this.onSyncSuccess = onSyncSuccess|| function(){}; + this.onSyncFailure = onSyncFailure|| function(){}; + }, + + /** + Used to preserve state while pushing. + @private + @param pushData {object} Contains the following: 'name' with the collection name, + 'adapter' is an Adapter and 'data' is what we want to push. + */ + PushInstance = function(pushData) { + + this.pushData = pushData; + }, + + /** + Create advanced find query part + @public + */ + QueryPart = function QueryPart() { + if (!(this instanceof QueryPart)) { + return new QueryPart(); + } + }, + + /** + Handles creating query array for advanced find + */ + __pushSearchFieldAndValueDict = function(operation, searchField, value) { + if (!Array.isArray(this[constant.QUERYPART + operation])) { + this[constant.QUERYPART + operation] = []; + } + + var obj = {}; + obj[searchField] = value; + this[constant.QUERYPART + operation].push(obj); + return this; + }, + + /** + Handles failure cases (mostly for push), where we need to do the following: + - Populate an error object + - Call the failure callback + - Async reject of the promise + @private + @param errObj {err: object, errCode: int, onFailure: function, deferred: object, doc: } + An object containing all the data we need + */ + __handleErrorReturn = function(errObj) { + var e = errObj.err || {}; + + e.err = errObj.errCode; + e.msg = WL.JSONStore.getErrorMessage(errObj.errCode); + + setTimeout(function() { + errObj.onFailure(e.err, e.doc); + errObj.deferred.reject(new ErrorObject(e)); + }, 0); + + return errObj.deferred.promise(); + }, + + /** + Handles success cases (mostly for push), where we need to do the following: + - Call the success callback + - Async resolve of the promise + @private + @param errObj {retCode: < to callback, ret val>, retVal: , onSuccess: function, deferred: object} + An object containing all the data we need + */ + __handleSuccessReturn = function(obj) { + + setTimeout(function() { + obj.onSuccess(obj.retCode); + obj.deferred.resolve(obj.retVal); + }, 0); + + return obj.deferred.promise(); + }, + + /** + Log deprecated message + @private + @param old {string} old function + @param alt {string} new function + */ + + __logDeprecatedMessage = function(old, alt) { + if (check.isObject(WL) && check.isObject(WL.Logger) && check.isFunction(WL.Logger.debug)) { + WL.Logger.ctx({ + pkg: constant.PKG_NAME + }).debug('[Deprecated] ' + old + ', use ' + alt + ' instead.'); + } + }, + + /** + Used to generated onSuccess and onFailure callbacks + @private + @param options {object} should contain `onSuccess` and `onFailure` keys + @param src {string} name of the function that generated the callbacks (ie. find, replace, etc.) + @return {object} An object that has an onSuccess and onFailure key. + */ + __generateCallbacks = function(options, src, collectionName, username, deferred) { + + return cb.generate(options, EVENT_LABELS, src, collectionName, username, deferred); + }, + + /** + Used to check if we can call down to native with data after it's validated an put in an array. + @private + @param arr {array} should contain `onSuccess` and `onFailure` keys + @param callbacks {string} An object that has an onSuccess and onFailure key. + @return {object} True if there's something in the array, false otherwise. + */ + __validDataExists = function(arr, callbacks) { + + if (arr.length > 0) { + + return true; + + } else { + + setTimeout(function() { + callbacks.onFailure(constant.BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ARRAY_OF_DOCUMENTS); + }, 0); + + return false; + } + }, + + /** + If data is a an object or an array of objects, push it to an array. + @private + @param data {object or array of objects} Object we are going to check. + @return {array} Array with objects in every index + */ + __getDataArray = function(data) { + + var arrayOfObjects = [], + i = 0; + + if (check.isObject(data)) { + + arrayOfObjects.push(data); + + } else if (check.isArrayOfObjects(data)) { + + for (; i < data.length; i++) { + + if (check.isObject(data[i])) { + + arrayOfObjects.push(data[i]); + + } else { + + return []; + } + } + } + + return arrayOfObjects; + }, + + /** + If data is a valid document, a simple object (query), an array of documents or an integer (_id) push it to an array. + @private + @param doc {document, simple object, array of documents or int} Valid values. + @param idOnly {boolean} Determines if we return {_id: [int]} or a whole document. + @return {array} Array with one of more of those in every index. + */ + __getQueryArray = function(doc, obj) { + + obj = obj || {}; + + var idQueryObj = {}, + arrayOfQueries = [], + i = 0, + len = 0, + idOnly = obj.idOnly || false, + fakeDoc = obj.fakeDoc || false, + idArray = obj.idArray || false, + isQueryValid = obj.isQueryValid || false; + + if (check.isValidDocument(doc)) { + + if (idOnly) { + idQueryObj[constant.ID_KEY] = doc[constant.ID_KEY]; + arrayOfQueries.push(_.cloneDeep(idQueryObj)); + } else { + arrayOfQueries.push(doc); + } + + } else if (isQueryValid && check.isSimpleObject(doc)) { + + arrayOfQueries.push(doc); + + } else if (check.isArrayOfObjects(doc)) { + + for (; i < doc.length; i++) { + + if (check.isValidDocument(doc[i])) { + + /*jshint maxdepth:4*/ + if (idOnly) { + idQueryObj[constant.ID_KEY] = doc[i][constant.ID_KEY]; + arrayOfQueries.push(_.cloneDeep(idQueryObj)); + } else { + arrayOfQueries.push(doc[i]); + } + + } else if(isQueryValid && check.isSimpleObject(doc[i])) { + arrayOfQueries.push(doc[i]); + } else { + return []; + } + } + + } else if (idOnly && check.isInt(doc)) { + + idQueryObj[constant.ID_KEY] = doc; + + if (fakeDoc) { //Special case for push, we need this to pass validation + idQueryObj[constant.JSON_DATA_KEY] = {}; + } + + arrayOfQueries.push(_.cloneDeep(idQueryObj)); + + } else if (idArray && check.isArrayOfInts(doc)) { + len = doc.length; + while (len--) { + arrayOfQueries.push({ + _id: doc[len], + json: {} + }); + } + } + + return arrayOfQueries; + }, + + /** + Holds the push logic that is used via push and pushSelected. + @private + @param [options] {Options} + @param name {string} Collection name. + @param searchFields {searchFields} + */ + __push = function(options, name, username, adapter, doc) { + + var deferred = $.Deferred(), + collectionAdapter = adapter, + collectionName = name, + usr = username || '', + errObject = { + src: 'push', + col: collectionName, + usr: username + }, + callbacks = __generateCallbacks(options, 'push', collectionName, usr, null), + arrayOfObjects = [], + arr = [], + retarr = [], + i = 0, + len = 0, + inst = {}, + + allDirtySuccess = function(data) { + + len = data.length; + + if (len < 1) { + + return __handleSuccessReturn({ + retCode: constant.SUCCESS, + retVal: retarr, + onSuccess: callbacks.onSuccess, + deferred: deferred + }); + + } + + //Time to push the data Array we got back + for (i = 0; i < len; i++) { + + inst = new PushInstance({ + name: collectionName, + adapter: collectionAdapter, + data: data[i], + onSuccess: callbacks.onSuccess, + onFailure: callbacks.onFailure, + usr: usr + }); + arr.push(inst.sendResourceRequest({ + timeout: adapter.timeout + })); + } + + $.when.apply($, arr).then(function() { + + var args = Array.prototype.slice.call(arguments); + var errors = []; + var docs = []; + _.each(args, function(a) { + if (a instanceof ErrorObject) { + errors.push(a); + } else { + docs.push(a); + } + }); + + if (docs.length > 0) { + db.markpushed(collectionName, docs) + + .then(function() { + //backwards compat. go to the succ cb for each doc, with a 0. + var l = docs.length; + while (l--) { + callbacks.onSuccess(constant.SUCCESS); + } + + if (errors.length > 0) { + deferred.resolve(errors); + } else { + deferred.resolve(retarr); + } + }) + .fail(function(badDoc) { + //backwards compat. go to the succ cb for each doc, with a 0. + + var rc = constant.COULD_NOT_MARK_DOCUMENT_PUSHED; + callbacks.onFailure(rc); + + $.extend(errObject, { + err: rc, + msg: WL.JSONStore.getErrorMessage(rc), + doc: badDoc + }); + errors.push(new ErrorObject(errObject)); + deferred.resolve(errors); + }); + } else { + //All the docs we tried to push were errors, we've already + //called the error cb N times, so resolve the promise + //with all the failed docs + deferred.resolve(errors); + } + + }); + }, + + allDirtyFailure = function(data) { + // If we get the special "can not access the database manager" failure, then return that + // otherwise, return we could not find dirty doc + + if (data === constant.PERSISTENT_STORE_NOT_OPEN) { + + return __handleErrorReturn({ + err: errObject, + errCode: data, + onFailure: callbacks.onFailure, + deferred: deferred + }); + + } else { + + return __handleErrorReturn({ + err: errObject, + errCode: constant.FAILED_TO_GET_UNPUSHED_DOCUMENTS_FROM_DB, + onFailure: callbacks.onFailure, + deferred: deferred + }); + + } + + }; + + //Checks if collectionAdapter is an object with a key 'name' that has a string value. + if (!check.isValidAdapter(collectionAdapter)) { + + return __handleErrorReturn({ + err: errObject, + errCode: constant.NO_ADAPTER_LINKED_TO_COLLECTION, + onFailure: callbacks.onFailure, + deferred: deferred + }); + } + + if (check.isUndefined(doc) && doc !== null) { + + //Get ALL of the dirty records in the database + db.allDirty(collectionName, [], { + onSuccess: allDirtySuccess, + onFailure: allDirtyFailure + }); + + } else if (check.isValidDocument(doc)) { + + db.allDirty(collectionName, [doc], { + onSuccess: allDirtySuccess, + onFailure: allDirtyFailure + }); + + } else if (check.isArrayOfObjects(doc)) { + + for (i = 0, len = doc.length; i < len; i++) { + + if (check.isValidDocument(doc[i])) { + + arrayOfObjects.push(doc[i]); + } else { + + errObject.data = doc[i]; + return __handleErrorReturn({ + err: errObject, + errCode: constant.BAD_PARAMETER_EXPECTED_DOCUMENT, + doc: doc[i], + onFailure: callbacks.onFailure, + deferred: deferred + }); + + } + } + + db.allDirty(collectionName, arrayOfObjects, { + onSuccess: allDirtySuccess, + onFailure: allDirtyFailure + }); + + } else { + //Trying to call push with invalid data + + return __handleErrorReturn({ + err: errObject, + errCode: constant.BAD_PARAMETER_EXPECTED_DOCUMENT_OR_ARRAY_OF_DOCUMENTS, + onFailure: callbacks.onFailure, + deferred: deferred + }); + } + + return deferred.promise(); + }, + + /** + Holds the store and add logic. + @private + @param name {string} Collection name. + @param searchFields {searchFields} + @param data {Object} Data we want to store or add. + @param [options] {Options} + */ + __store = function(name, username, searchFields, additionalSearchFields, data, options, onSyncSuccess, onSyncFailure){ + + var deferred = $.Deferred(), + arrayOfObjects = __getDataArray(data), + usr = username || '', + callbacks = __generateCallbacks(options, 'store', name, usr, deferred), + additionalSearchFieldsObj = options.additionalSearchFields, + syncActionReceiverName = '__WL_syncActionReceiverFor'+name; + + callbacks.isAdd = options.isAdd; + function actionReceiverForSyncAdd(received){ + if(received.data.success === 'true') + { + onSyncSuccess(received.data.message); + } + else if(received.data.success === 'false') + { + onSyncFailure(received.data.message); + } + else + { + onSyncFailure('Sync failed in native layer'); + } + WL.App.removeActionReceiver(syncActionReceiverName); + } + + if(onSyncSuccess !== undefined && onSyncFailure !== undefined) + { + WL.App.addActionReceiver(syncActionReceiverName, actionReceiverForSyncAdd); + } + + if (check.isSimpleObject(additionalSearchFieldsObj)) { + + if (check.isPartofSearchFields(additionalSearchFieldsObj, searchFields, additionalSearchFields)) { + + callbacks.additionalSearchFields = additionalSearchFieldsObj; + } else { + + callbacks.onFailure(constant.INVALID_ADD_INDEX_KEY); + } + } + + if (check.isArray(data) && data.length === 0) { + setTimeout(function() { + callbacks.onSuccess(0); + }, 0); + } else if (__validDataExists(arrayOfObjects, callbacks)) { + db.store(name, arrayOfObjects, callbacks); + } + + return deferred.promise(); + }, + + /** + Holds the replace and refresh logic. + @private + @param name {string} Collection name. + @param doc {Document} Document we want to replace or refresh. + @param [options] {Options} + */ + __replace = function(name, username, doc, options,onSyncSuccess,onSyncFailure) { + + var deferred = $.Deferred(), + arrayOfQueries = __getQueryArray(doc, { + idOnly: false, + isQueryValid: false + }), + usr = username || '', + callbacks = __generateCallbacks(options, 'replace', name, usr, deferred), + syncActionReceiverName = '__WL_syncActionReceiverFor'+name; + + callbacks.isRefresh = options.isRefresh; + + function actionReceiverForSyncReplace(received){ + if(received.data.success === 'true') + { + onSyncSuccess(received.data.message); + } + else if(received.data.success === 'false') + { + onSyncFailure(received.data.message); + } + else + { + onSyncFailure('Sync failed in native layer'); + } + WL.App.removeActionReceiver(syncActionReceiverName); + } + + if(onSyncSuccess !== undefined && onSyncFailure !== undefined) + { + WL.App.addActionReceiver(syncActionReceiverName, actionReceiverForSyncReplace); + } + + if (check.isEmptyArray(doc)) { + setTimeout(function() { + callbacks.onSuccess(0); + }); + } else if (__validDataExists(arrayOfQueries, callbacks)) { + db.replace(name, arrayOfQueries, callbacks); + } + + return deferred.promise(); + }, + + /** + Holds the remove and erase logic. + @private + @param name {string} Collection name. + @param doc {Document} Document we want to remove or erase. + @param [options] {Options} + */ + __remove = function(name, username, searchFields, additionalSearchFields, doc, options,onSyncSuccess,onSyncFailure) { + + var deferred = $.Deferred(), + arrayOfQueries = __getQueryArray(doc, { + idOnly: true, + idArray : true, + isQueryValid: true + }), + usr = username || '', + queries, + callbacks = __generateCallbacks(options, 'remove', name, usr, deferred), + syncActionReceiverName = '__WL_syncActionReceiverFor'+name; + + + function actionReceiverForSyncRemove(received){ + if(received.data.success === 'true') + { + onSyncSuccess(received.data.message); + } + else if(received.data.success === 'false') + { + onSyncFailure(received.data.message); + } + else + { + onSyncFailure('Sync failed in native layer'); + } + WL.App.removeActionReceiver(syncActionReceiverName); + } + + if(onSyncSuccess !== undefined && onSyncFailure !== undefined) + { + WL.App.addActionReceiver(syncActionReceiverName, actionReceiverForSyncRemove); + } + + callbacks.isErase = options.isErase; + callbacks.exact = check.isBoolean(options.exact) ? options.exact : false; + if (check.isEmptyArray(doc)) { + setTimeout(function() { + callbacks.onSuccess(0); + }, 0); + } else if (__validDataExists(arrayOfQueries, callbacks)) { + try { + queries = __handleQueryObject(arrayOfQueries, searchFields, additionalSearchFields); + db.remove(name, arrayOfQueries, callbacks); + } catch(rc) { + setTimeout(function () { + callbacks.onFailure(rc); + },0); + } + } + + return deferred.promise(); + }, + + __handleOptions = function(options, searchFields, additionalSearchFields) { + + var opts = {}; + var lowerCaseFilter = []; + + if (check.isObject(options)) { + + if (check.isBoolean(options.exact)) { + opts.exact = options.exact; + } + + if (check.isUndefined(options.limit) && check.isInt(options.offset)) { + throw constant.OFFSET_WITHOUT_LIMIT; + } + + if (check.isInt(options.limit)) { + + if (options.limit < 0) { + //If limit is negative, we can not have an offset + /*jshint maxdepth:4*/ + if (!check.isUndefined(options.offset)) { + throw constant.INVALID_LIMIT_OR_OFFSET; + } + } + + opts.limit = options.limit; + + if (check.isInt(options.offset)) { + + if (options.offset < 0) { + throw constant.INVALID_LIMIT_OR_OFFSET; + } + + opts.offset = options.offset; + } + } + + if (check.isArrayOfObjects(options.sort) && options.sort.length > 0) { + + //Check that each object in the sort array has only one property with the string "ASC" or "DESC": + for (var i = 0; i < options.sort.length; i++) { + + var sortObj = options.sort[i]; + + if (!check.isValidSortObject(sortObj, searchFields, additionalSearchFields)) { + throw constant.INVALID_SORT_OBJECT; + } + } + + opts.sort = options.sort; + } else if (!check.isUndefined(options.sort) && !(check.isArray(options.sort) && options.sort.length === 0)) { + throw constant.INVALID_SORT_OBJECT; + } + + //lowercase keys + if(check.isArray(options.filter)){ + _.each(options.filter, function(value) { + var newValue = value; + if(check.isString(value)) { + newValue = value.toLowerCase(); + } + lowerCaseFilter.push(newValue); + }); + } else { + lowerCaseFilter.push(options.filter); + } + + + + if (check.isArrayOfSAFields(lowerCaseFilter, searchFields, additionalSearchFields)) { + opts.filter = options.filter; + } else if (!check.isUndefined(options.filter)) { + throw constant.INVALID_FILTER_ARRAY; + } + + } + + return opts; + }, + + __handleQueryObject = function(query, searchFields, additionalSearchFields) { + + if (check.isSimpleObject(query)) { //Arrays are not valid objects + + if (check.isPartofSearchFields(query, searchFields, additionalSearchFields)) { + return [query]; + + } else { + throw constant.INVALID_SEARCH_FIELD; + } + + } else if (check.isArrayOfObjects(query)) { + + for (var i = 0, len = query.length; i < len; i++) { + + if (!check.isPartofSearchFields(query[i], searchFields, additionalSearchFields)) { + throw constant.INVALID_SEARCH_FIELD; + } + } + + return query; + + } else if (check.isArray(query) && query.length < 1) { + + return []; + + } else { + + throw constant.BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT; + } + }, + + /** + Holds the find and findAll logic. + @private + @param name {string} Collection name. + @param query {Query} Object with a search key from the searchFields + and a search term in the value. FindAll passes an empty object. + @param [options] {Options} + */ + __find = function(name, username, query, searchFields, additionalSearchFields, options) { + + var deferred = $.Deferred(), + queries, + callbacks = __generateCallbacks(options, 'find', name, username || '', deferred); + + callbacks.exact = false; + + try { + //Limit and offset are appended to the callbacks obj + _.extend(callbacks, __handleOptions(options, searchFields, additionalSearchFields)); + + queries = __handleQueryObject(query, searchFields, additionalSearchFields); + + if (queries.length === 0) { //Empty query array passed + + setTimeout(function() { + callbacks.onSuccess(queries); + }, 0); + + } else { + + db.find(name, queries, callbacks); + } + + } catch (rc) { + setTimeout(function() { + callbacks.onFailure(rc); + }, 0); + } + + return deferred.promise(); + }, + + /** + Transaction operations, only work on Android and iOS environments. + */ + _startTransaction = function() { + + var dfd = $.Deferred(), + callbacks = __generateCallbacks(null, 'startTransaction', '', '', dfd); + + db.startTransaction(callbacks); + + return dfd.promise(); + }, + + _commitTransaction = function() { + + var dfd = $.Deferred(), + callbacks = __generateCallbacks(null, 'commitTransaction', '', '', dfd); + + db.commitTransaction(callbacks); + + return dfd.promise(); + }, + + _rollbackTransaction = function() { + + var dfd = $.Deferred(), + callbacks = __generateCallbacks(null, 'rollbackTransaction', '', '', dfd); + + db.rollbackTransaction(callbacks); + + return dfd.promise(); + }, + + _fileInfo = function() { + + var dfd = $.Deferred(), + callbacks = __generateCallbacks(null, 'fileInfo', '', '', dfd); + + db.fileInfo(callbacks); + + return dfd.promise(); + }, + + /** + Creates a Document. + */ + _documentify = function(id, data) { + + var documentId = Number(id), + documentToReturn = {}; + + if (!check.isInt(documentId)) { + + return constant.BAD_PARAMETER_EXPECTED_INT; + } + + if (!check.isObject(data)) { + + return constant.BAD_PARAMETER_EXPECTED_OBJECT; + } + + documentToReturn[constant.ID_KEY] = documentId; + documentToReturn[constant.JSON_DATA_KEY] = data; + + return documentToReturn; + }, + + /** + Sets the password used to generate keys to encrypt date stored locally on the device. + */ + _usePassword = function(pwd) { + + __logDeprecatedMessage('WL.JSONStore.usePassword', 'WL.JSONStore.init'); + + if (check.isString(pwd) && pwd.length > 0) { + + PWD = pwd; + return true; + + } else { + + return false; + + } + }, + + /** + Removes the password from memory. + */ + _clearPassword = function() { + + __logDeprecatedMessage('WL.JSONStore.clearPassword', 'WL.JSONStore.init'); + + if (check.isUndefined(PWD)) { + return false; + } else { + PWD = null; + return (PWD === null); + } + + }, + + /** + Closes all the collections in JSONStore. + */ + _closeAll = function(options) { + + COLLECTIONS = {}; + + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'closeAll', '', '', deferred); + + db.closeAll(callbacks); + + return deferred.promise(); + }, + + /** + Changes the password for the internal storage. + */ + _changePassword = function(oldPW, newPW, user, options) { + + var deferred = $.Deferred(); + + //Preserve legacy signature of changePassword(oldPW, newPW, options) + var opts = { + onSuccess: function() {}, + onFailure: function() {} + }, + usr = WL.constant.DEFAULT_USERNAME, + rc = constant.SUCCESS; + + if (check.isString(user) && user.length > 0) { + usr = user; + + if (!check.isAlphaNumeric(usr) || check.isReservedWord(usr)) { + rc = constant.INVALID_USERNAME; + } + } + + if (check.isObject(user)) { + opts.onSuccess = (typeof user.onSuccess === 'function') ? user.onSuccess : function() {}; + opts.onFailure = (typeof user.onFailure === 'function') ? user.onFailure : function() {}; + + } else if (check.isObject(options)) { + opts.onSuccess = (typeof options.onSuccess === 'function') ? options.onSuccess : function() {}; + opts.onFailure = (typeof options.onFailure === 'function') ? options.onFailure : function() {}; + } + + var callbacks = __generateCallbacks(opts, 'changePassword', '', usr, deferred); + + if (rc !== constant.SUCCESS) { + + //Invalid username + setTimeout(function() { + callbacks.onFailure(rc); + }, 0); + return deferred.promise(); + } + + if (check.isString(oldPW) && oldPW.length > 0 && + check.isString(newPW) && newPW.length > 0) { + + db.changePassword(oldPW, newPW, usr, callbacks); + + } else { + // Both Passwords must be an alphanumeric string of length greater than zero + setTimeout(function() { + callbacks.onFailure(constant.INVALID_PASSWORD_EXPECTED_ALPHANUMERIC_STRING_WITH_LENGTH_GREATER_THAN_ZERO); + }, 0); + } + + return deferred.promise(); + }, + + /** + A complete data wipe for all users, destroys the internal storage and clears the secuirty artifacts. + */ + _destroy = function(optionsOrUsername) { + + COLLECTIONS = {}; + + var username, + options; + + //A username was passed + if (typeof optionsOrUsername === 'string') { + username = optionsOrUsername; + + //An options object was passed, probably something like: {onSuccess: func, onFailure: func} + } else if (typeof optionsOrUsername === 'object') { + options = optionsOrUsername; + } + + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'destroy', '', '', deferred); + + db.destroy(callbacks, username); + + return deferred; + }, + + /** + Returns the message associated with a JSONStore-specific status/error code. + */ + _getErrorMessage = function(statusCode) { + + if (!check.isInt(statusCode)) { + return constant.NOT_FOUND; + } + + return ERROR[statusCode] || constant.NOT_FOUND; + }, + + /** + Returns a JSONStoreInstance linked to a collection or undefined if the instance is not found. + */ + _get = function(collection) { + + return COLLECTIONS[collection]; + }, + + /** + Initializes a set of collections. + */ + + _init = function(collections, options) { + + options = options || {}; + + if (check.isBoolean(options.clear) && options.clear) { + COLLECTIONS = {}; + } + var arrp = [], + def = $.Deferred(), + col, + first, + firstPromise, + otherPromise, + errCode, + returnError = function(err) { + COLLECTIONS = {}; + def.reject(err); + }, + setOptions = function(key) { + options.adapter = collections[key].adapter; + options.additionalSearchFields = collections[key].additionalSearchFields || {}; + if(collections[key].sync !== undefined){ + if(collections[key].sync.syncPolicy !== undefined){ + options.syncPolicy = collections[key].sync.syncPolicy; + } + else{ + options.syncPolicy = 2;//2 means SYNC_NONE + } + options.syncAdapterPath = collections[key].sync.syncAdapterPath || ''; + options.onSyncSuccess = collections[key].sync.onSyncSuccess || function(){}; + options.onSyncFailure = collections[key].sync.onSyncFailure || function(){}; + } + }, + addToCollections = function(key, value) { + COLLECTIONS[key] = value; + }, + genSearchFields = function(key) { + return collections[key].searchFields || {}; + }, + errorFoundReject = function(faultyPromise, col) { + errCode = check.isInt(faultyPromise) ? faultyPromise : -100; + + def.reject(new ErrorObject({ + err: errCode, + msg: WL.JSONStore.getErrorMessage(errCode), + src: 'initCollection', + usr: options.username || WL.constant.DEFAULT_USERNAME, + col: col + })); + }; + + if (check.countKeys(collections) < 1) { + + setTimeout(function() { + def.resolve({}); + }, 0); + + } else { + + /*jshint forin:false*/ + for (first in collections) { + break; + } + + setOptions(first); + + col = _provisionCollection(first, genSearchFields(first), options); + + addToCollections(first, col); + + firstPromise = col.promise; + + if (check.isUndefined(firstPromise)) { + + errorFoundReject(col, first); + + } else { + + firstPromise.then(function() { + + for (var name in collections) { + + if (first !== name) { + setOptions(name); + + //The password was already used during the first init, we clear it here + //Commenting below code because its causing issue whhile initializing multiple collection with same password + //options.password = null; + + col = _provisionCollection(name, genSearchFields(name), options); + otherPromise = col.promise; + + if (check.isUndefined(otherPromise)) { + + errorFoundReject(col, name); + + } else { + arrp.push(col.promise); + addToCollections(name, col); + } + } + } + + $.when.apply(this, arrp) + + .done(function() { + def.resolve(COLLECTIONS); + }) + + .fail(returnError); + }) + + .fail(returnError); + } + + } + + return def; + }, + + /** + Creates an new object to interact with a single collection. + */ + _provisionCollection = function(name, searchFields, options) { + + var instance, + deferred = $.Deferred(), + collectionAdapter, + collectionName = '', + username = WL.constant.DEFAULT_USERNAME, + collectionsearchFields = {}, + collectionAdditionalSearchFields = {}, + collectionOptions = {}, + dropCollection = false, + fipsEnabled = WL.Client.__state().enableFIPS, + localKeyGen = false, + analytics = false, + collectionPassword = '', + secureRandom = '', + collectionPBKDF2Iterations = 10000, + key = '', + syncPolicy = 2, + syncAdapterPath = '', + syncActionReceiverName = '__WL_syncActionReceiverFor'+name, + hasOwn = Object.prototype.hasOwnProperty, + callbacks = __generateCallbacks(options, 'initCollection', name, username, deferred); + + if (check.isObject(options)) { + + if (check.isBoolean(options.analytics)) { + analytics = options.analytics; + } + + if (check.isBoolean(options.dropCollection)) { + dropCollection = options.dropCollection; + } + + if (check.isBoolean(options.fipsEnabled)) { + WL.Logger.ctx({ + pkg: constant.PKG_NAME + }).warn('fipsEnabled option is deprecated. Use initOptions.enableFIPS instead.'); + } + + if (check.isBoolean(options.localKeyGen)) { + localKeyGen = options.localKeyGen; + } + + if (check.isString(options.username) && options.username.length > 0) { + + username = options.username; + + if (!check.isAlphaNumeric(username) || check.isReservedWord(username)) { + + //Those are default usernames that are used only in old versions of JSONStore + //and the special case where it's not an alphanumeric string + callbacks.onFailure(constant.INVALID_USERNAME); + return constant.INVALID_USERNAME; + } + + //Regenerate callback using the correct username + callbacks = __generateCallbacks(options, 'initCollection', name, username, deferred); + } + + //check if the password key exists + if (!check.isUndefined(PWD)) { + //validate passswrd + if (check.isString(PWD) && PWD.length > 0) { + collectionPassword = PWD; + } else { + + callbacks.onFailure(constant.INVALID_PASSWORD_EXPECTED_ALPHANUMERIC_STRING_WITH_LENGTH_GREATER_THAN_ZERO); + return constant.INVALID_PASSWORD_EXPECTED_ALPHANUMERIC_STRING_WITH_LENGTH_GREATER_THAN_ZERO; + } + } + + if (check.isString(options.password) && options.password.length > 0) { + collectionPassword = options.password; + } + + if (check.isValidAdapter(options.adapter)) { + collectionAdapter = options.adapter; + } + + if (check.isNumber(options.syncPolicy)) { + syncPolicy = options.syncPolicy; + } + } + + //Collection names need be alphanumeric and start with /[a-z]/i + if (check.isAlphaNumeric(name) && !check.isNumber(name.charAt(0))) { + collectionName = name; + } else { + + callbacks.onFailure(constant.BAD_PARAMETER_EXPECTED_ALPHANUMERIC_STRING); + return constant.BAD_PARAMETER_EXPECTED_ALPHANUMERIC_STRING; + } + + //searchFields + if (check.isSimpleObject(searchFields) && !check.containsDuplicateKeys(searchFields)) { + + if (!check.isValidSchemaObject(searchFields)) { + callbacks.onFailure(constant.INVALID_SEARCH_FIELD_TYPES); + return constant.INVALID_SEARCH_FIELD_TYPES; + } + + //Make lowercase + for (key in searchFields) { + if (hasOwn.call(searchFields, key)) { + collectionsearchFields[key.toLocaleLowerCase()] = searchFields[key]; + } + } + + } else { + callbacks.onFailure(constant.BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT); + return constant.BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT; + } + + //Additional search fields (optional parameter) + if (check.isObject(options) && !check.isUndefined(options.additionalSearchFields)) { + + if (check.isSimpleObject(options.additionalSearchFields) && !check.containsDuplicateKeys(options.additionalSearchFields)) { + + //Make lowercase + for (key in options.additionalSearchFields) { + /*jshint maxdepth:4*/ + if (hasOwn.call(options.additionalSearchFields, key)) { + collectionAdditionalSearchFields[key.toLocaleLowerCase()] = options.additionalSearchFields[key]; + } + } + + } else { + callbacks.onFailure(constant.BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT); + return constant.BAD_PARAMETER_EXPECTED_SIMPLE_OBJECT; + } + + if (check.countKeys(collectionAdditionalSearchFields) + check.countKeys(collectionsearchFields) !== + check.countKeys(check.mergeObjects(collectionAdditionalSearchFields, collectionsearchFields))) { + callbacks.onFailure(constant.INVALID_SEARCH_FIELD); + return constant.INVALID_SEARCH_FIELD; + } + } + + if(check.isObject(options) && !check.isUndefined(options.pbkdf2Iterations) && check.isInt(options.pbkdf2Iterations)){ + collectionPBKDF2Iterations = options.pbkdf2Iterations; + } + + if(check.isObject(options) && !check.isUndefined(options.syncAdapterPath) && check.isString(options.syncAdapterPath)){ + syncAdapterPath = options.syncAdapterPath; + } + + if(check.isObject(options) && !check.isUndefined(options.syncPolicy) && check.isInt(options.syncPolicy)){ + syncPolicy = options.syncPolicy; + } + + if(syncPolicy!== 2){ + instance = new JSONStoreInstance(collectionName, collectionsearchFields, collectionAdapter, + collectionAdditionalSearchFields, username, deferred, options.onSyncSuccess, options.onSyncFailure); + }else{ + instance = new JSONStoreInstance(collectionName, collectionsearchFields, collectionAdapter, + collectionAdditionalSearchFields, username, deferred); + } + + //wi 116806 - Adding syncpolicy in options to accommodate couchdb-jsonstore sync + collectionOptions = { + dropCollection: dropCollection, + analytics: analytics, + collectionPassword: collectionPassword, + additionalSearchFields: collectionAdditionalSearchFields, + username: username, + localKeyGen: localKeyGen, + fipsEnabled: fipsEnabled, + onSuccess: callbacks.onSuccess, + onFailure: callbacks.onFailure, + secureRandom: secureRandom, + pbkdf2Iterations : collectionPBKDF2Iterations, + syncPolicy : syncPolicy, + syncAdapterPath: syncAdapterPath, + syncActionReceiverName: syncActionReceiverName + }; + + //wi 116664 - Adding action receiver for sync result of couchdb-jsonstore sync if the syncPolicy is set to SYNC_DONWSTREAM + //Sends message from native to appropriate callback and removes the previously registered action receiver. + // jshint unused:false + function actionReceiverForSyncInit(received){ + try{ + if(received.data.success === 'true') + { + options.onSyncSuccess(received.data.message); + } + else if(received.data.success === 'false') + { + options.onSyncFailure(received.data.message); + } + else + { + options.onSyncFailure('Sync failed in native layer'); + } + WL.App.removeActionReceiver(syncActionReceiverName); + }catch(error){ + console.log('Error occurred in action receiver for sync : ' + JSON.stringify(error)); + } + } + //wi 116664 - If sync policy is not SYNC_NONE, adding an action receiver + if(syncPolicy !== 2){ + try{ + WL.App.addActionReceiver(syncActionReceiverName, actionReceiverForSyncInit); + }catch(error){ + console.log('Exception occurred while adding action receiver for sync : ' + JSON.stringify(error)); + } + } + + + //ANDROID-FIX: Provision blocks and returns before 'return instance' + setTimeout(function() { + + if (collectionPassword.length > 0 && !localKeyGen) { + + db.isKeyGenReq(username, function(res) { + + /*jshint eqeqeq:false*/ + if (res == constant.KEYGEN_NOT_REQUIERED) { + + //No need to get a secure random + db.provision(collectionName, collectionsearchFields, collectionOptions); + + } else { + WL.App.__SecureRandom( + function(res) { + collectionOptions.secureRandom = '' + res; + db.provision(collectionName, collectionsearchFields, collectionOptions); + },function(){ + callbacks.onFailure(constant.COULD_NOT_GET_SECURE_KEY); + return constant.COULD_NOT_GET_SECURE_KEY; + } + ); + } + }); + } else { + + //No data encryption required + db.provision(collectionName, collectionsearchFields, collectionOptions); + } + + }, 0); + + return instance; + }, + + /** + See _provisionCollection, the code below is used to deal with load = true. + @private + */ + _initCollection = function(name, searchFields, options) { + + var instance; + + if (check.isObject(options) && check.isBoolean(options.load) && + options.load && check.isObject(options.adapter) && + check.isValidLoadObject(options.adapter.load)) { + + var userOnSuccessCallback = function() {}, + userOnFailureCallback = function() {}, + + provisionSuccessData = 0, + + onLoadSuccessCallback = function() { + + userOnSuccessCallback(provisionSuccessData); + + }, + + onCountSuccessCallback = function(count) { + + if (count === 0) { + instance.load({ + onFailure: userOnFailureCallback, + onSuccess: onLoadSuccessCallback + }); + } else { + userOnSuccessCallback(provisionSuccessData); + } + + }, + + onProvisionSuccessCallback = function(data) { + + provisionSuccessData = data; + + instance.count({ + onFailure: userOnFailureCallback, + onSuccess: onCountSuccessCallback + }); + + }; + + if (check.isFunction(options.onSuccess)) { + userOnSuccessCallback = options.onSuccess; + } + + if (check.isFunction(options.onFailure)) { + userOnFailureCallback = options.onFailure; + } + + options.onSuccess = onProvisionSuccessCallback; + options.onFailure = userOnFailureCallback; + + instance = _provisionCollection(name, searchFields, options); + + } else { + + instance = _provisionCollection(name, searchFields, options); + } + + return instance; + + }; + + PushInstance.prototype = { + + /** + Invoke the adapter linked to the collection. + @private + */ + sendResourceRequest: function(options) { + + var deferred = $.Deferred(), + collectionName = this.pushData.name, + usr = this.pushData.usr, + errorObject = { + src: 'push', + col: collectionName, + usr: usr + }, + onFailure = this.pushData.onFailure, + collectionAdapter = this.pushData.adapter, + collectionDocument = this.pushData.data[constant.JSON_DATA_KEY], + documentId = this.pushData.data[constant.ID_KEY], + documentOperation = this.pushData.data[constant.OPERATION_KEY], + documentIdAndOperationObj = {}, + resourceRequest, + ipOpts = {}, + + adapterSuccess = function(data) { + + documentIdAndOperationObj[constant.ID_KEY] = documentId; + documentIdAndOperationObj[constant.OPERATION_KEY] = documentOperation; + + var acceptCondition = check.isUndefined(collectionAdapter.accept) || + collectionAdapter.accept(data, collectionDocument); + + if (acceptCondition) { + deferred.resolve(documentIdAndOperationObj); + + } else { + + errorObject.err = constant.ACCEPT_CONDITION_FAILED; + errorObject.msg = WL.JSONStore.getErrorMessage(constant.ACCEPT_CONDITION_FAILED); + errorObject.doc = collectionDocument; + deferred.resolve(new ErrorObject(errorObject)); + adapterFailure(data); + } + }, + + adapterFailure = function(data) { + if (deferred.state() !== 'resolved') { + errorObject.err = constant.ADAPTER_FAILURE; + errorObject.msg = WL.JSONStore.getErrorMessage(constant.ADAPTER_FAILURE); + errorObject.res = data; + errorObject.doc = collectionDocument; + + deferred.resolve(new ErrorObject(errorObject)); + } + //Must call the callback with rc 12 to preserve backwards compatibility with + //th pre-promises api + onFailure(constant.ADAPTER_FAILURE, data); + }, + + invocationData = { + adapter: collectionAdapter.name, + procedure: collectionAdapter[documentOperation], + parameters: [JSON.stringify(collectionDocument)] + }; + + if (check.isString(invocationData.procedure) && invocationData.procedure.length > 0) { + ipOpts.onSuccess = adapterSuccess; + ipOpts.onFailure = adapterFailure; + + if (!check.isUndefined(options.timeout)) { + ipOpts.timeout = options.timeout; + } + + resourceRequest = new WLResourceRequest('adapters/' + invocationData.adapter + '/' + invocationData.procedure, WLResourceRequest.POST); + if(invocationData && invocationData.parameters){ + resourceRequest.setQueryParameter('params', invocationData.parameters); + } + resourceRequest.setHeader('Content-Type','application/x-www-form-urlencoded'); + resourceRequest.send().then(ipOpts.onSuccess, ipOpts.onFailure); + + } else { + + errorObject.err = constant.UNDEFINED_PUSH_OPERATION; + errorObject.msg = WL.JSONStore.getErrorMessage(constant.UNDEFINED_PUSH_OPERATION); + errorObject.doc = collectionDocument; + + deferred.resolve(new ErrorObject(errorObject)); + onFailure(constant.UNDEFINED_PUSH_OPERATION, documentOperation); + } + + return deferred.promise(); + } + + }; + + // generate generic prototypes based on query operations (ie: like, between, etc) + _.each(advanceFindQueryOperations, function(operation) { + QueryPart.prototype[operation] = (function(op) { + return function( /*searchField, value*/ ) { + var args = Array.prototype.slice.call(arguments, 0); + args.unshift(op); + return __pushSearchFieldAndValueDict.apply(this, args); + }; + }(operation)); + }); + + JSONStoreInstance.prototype = { + + /** + Returns documents stored in the collection that match the query. + */ + find: function(query, options) { + + return __find(this.name, this.username, query, this.searchFields, this.additionalSearchFields, options); + + }, + + /** + Returns documents stored in the collection that match the advanced find query. + */ + advancedFind: function(query, options) { + + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'advancedFind', this.name, this.username, deferred); + + callbacks = _.extend(callbacks, options); + + if(check.isSimpleObject(query) && _.keys(query).length === 0) + { + query = [query]; + } else if (check.isEmptyArray(query)) { + query = [{}]; + } + db.advancedFind(this.name, query, callbacks); + + return deferred.promise(); + }, + + /** + Returns one or more documents that match the id or ids supplied to the function. + */ + + findById: function(id, options) { + + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'findById', this.name, this.username, deferred), + callNative = false, + param = [], + validArrayOfInts = function() { + for (var i = 0, len = id.length; i < len; i++) { + + if (!check.isInt(id[i])) { + return false; + } + + } + return true; + }; + + //Check for valid values + if (check.isInt(id)) { + callNative = true; + param = [id]; + } + + if (check.isArray(id) && id.length > 0) { + callNative = validArrayOfInts(id); + param = id; + } + + //Decide if we want to call native or return an error + if (callNative) { + db.findById(this.name, param, callbacks); + } else { + callbacks.onFailure(constant.INVALID_PARAMETER_FOR_FIND_BY_ID); + } + + return deferred.promise(); + + }, + + /** + Syncs with the remote Cloudant DB based on sync policy set while initializing the collection + */ + sync: function() { + var options = {}, + that = this, + deferred = $.Deferred(), + usr = this.username || '', + callbacks = __generateCallbacks(options, 'sync', this.name, usr, deferred), + syncActionReceiverName = '__WL_syncActionReceiverFor'+this.name; + + function actionReceiverForSync(received){ + if(received.data.success === 'true'){ + that.onSyncSuccess(received.data.message); + } + else if(received.data.success === 'false'){ + that.onSyncFailure(received.data.message); + } + else{ + that.onSyncFailure('Sync failed in native layer'); + } + WL.App.removeActionReceiver(syncActionReceiverName); + } + + if(this.onSyncSuccess !== undefined && this.onSyncFailure !== undefined){ + WL.App.addActionReceiver(syncActionReceiverName, actionReceiverForSync); + } + + db.sync(this.name, callbacks); + return deferred.promise(); + }, + + /** + Returns all the documents stored in a collection. + */ + findAll: function(options) { + return __find(this.name, this.username, {}, this.searchFields, this.additionalSearchFields, options); + }, + + /** + Used to initially load JSON objects into a collection as Documents. + */ + store: function(data, options) { + + __logDeprecatedMessage('collection.store(doc)', 'collection.add(doc, {push: false})'); + + options = options || {}; + + if (check.isUndefined(options.isAdd)) { + options.isAdd = false; + } + + return __store(this.name, this.username, this.searchFields, this.additionalSearchFields, data, options,this.onSyncSuccess, this.onSyncFailure); + + }, + + /** + Adds data to a collection, creating a new Document(s). + */ + add: function(data, options) { + + options = options || {}; + + if (check.isUndefined(options.isAdd)) { + options.isAdd = true; + } + + if (check.isBoolean(options.push)) { + options.isAdd = options.push; + } + + if (check.isBoolean(options.markDirty)) { + options.isAdd = options.markDirty; + } + + return __store(this.name, this.username, this.searchFields, this.additionalSearchFields, data, options,this.onSyncSuccess,this.onSyncFailure); + + }, + + /** + Replaces a Document with another Document. + */ + replace: function(doc, options) { + + options = options || {}; + + if (check.isUndefined(options.isRefresh)) { + options.isRefresh = false; + } + + if (check.isBoolean(options.push)) { + options.isRefresh = !options.push; + } + + if (check.isBoolean(options.markDirty)) { + options.isRefresh = !options.markDirty; + } + + return __replace(this.name, this.username, doc, options,this.onSyncSuccess,this.onSyncFailure); + }, + + /** + Replaces a Document with another Document just like `replace`, but it does + not mark that change to push to the back end via an adapter. + */ + refresh: function(doc, options) { + + __logDeprecatedMessage('collection.refresh(doc)', 'collection.replace(doc, {push: false})'); + + options = options || {}; + + if (check.isUndefined(options.isRefresh)) { + options.isRefresh = true; + } + + return __replace(this.name, this.username, doc, options,this.onSyncSuccess,this.onSyncFailure); + }, + + /** + Marks 1 or more Documents as removed from a collection. + */ + remove: function(doc, options) { + + options = options || {}; + + if (check.isUndefined(options.isErase)) { + options.isErase = false; + } + + if (check.isBoolean(options.push)) { + options.isErase = !options.push; + } + + if (check.isBoolean(options.markDirty)) { + options.isErase = !options.markDirty; + } + + return __remove(this.name, this.username, this.searchFields, this.additionalSearchFields, doc, options,this.onSyncSuccess,this.onSyncFailure); + + }, + + /** + Same as `remove` but will really remove the document from the internal storage instead + of marking it for removal. + */ + erase: function(doc, options) { + + __logDeprecatedMessage('collection.erase(doc)', 'collection.remove(doc, {push: false})'); + + options = options || {}; + + if (check.isUndefined(options.isErase)) { + options.isErase = true; + } + + return __remove(this.name, this.username, this.searchFields, this.additionalSearchFields, doc, options,this.onSyncSuccess,this.onSyncFailure); + + }, + + /** + Push the collection with an Adapter. + */ + push: function(options) { + + options = options || {}; + + var arrayOfDocs = __getQueryArray(options, { + idOnly: true, + idArray: true, + fakeDoc: true, + isQueryValid: false + }); + + if (arrayOfDocs.length > 0) { + return __push(options, this.name, this.username, this.adapter, arrayOfDocs); + } + + return __push(options, this.name, this.username, this.adapter); + }, + + /** + Pushes only the selected Documents. + */ + pushSelected: function(doc, options) { + return __push(options, this.name, this.username, this.adapter, doc); + }, + + /** + Determines if a Document is pushed. + */ + isPushRequired: function(doc, options) { + + var deferred = $.Deferred(), + arrayOfQueries = __getQueryArray(doc, { + idOnly: true, + isQueryValid: false + }), + callbacks = __generateCallbacks(options, 'isPushRequired', this.name, this.username, deferred); + + if (__validDataExists(arrayOfQueries, callbacks)) { + db.isPushRequired(this.name, arrayOfQueries[0], callbacks); + } + + return deferred.promise(); + }, + + isDirty: function() { + return this.isPushRequired.apply(this, arguments); + }, + + /** + Get all the Documents that are unpushed. + */ + getPushRequired: function(options) { + + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'getPushRequired', this.name, this.username, deferred); + + db.allDirty(this.name, [], callbacks); + + return deferred.promise(); + }, + + /** + Alias - Get all the Documents that are unpushed. + */ + getAllDirty: function() { + return this.getPushRequired.apply(this, arguments); + }, + + /** + Returns the number of documents not pushed. + */ + pushRequiredCount: function(options) { + + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'pushRequiredCount', this.name, this.username, deferred); + + db.pushRequiredCount(this.name, callbacks); + + return deferred.promise(); + }, + + countAllDirty: function() { + return this.pushRequiredCount.apply(this, arguments); + }, + + /** + Number of documents in the collection. + */ + count: function(query, options) { + + query = query || {}; + options = options || {}; + + var deferred = $.Deferred(), + callbacks = __generateCallbacks(query, 'count', this.name, this.username, deferred); + + query = _.omit(query, 'onSuccess', 'onFailure'); + callbacks = _.extend(callbacks, options); + options = _.defaults(callbacks, { + exact: false + }); + + db.count(this.name, options, query); + + return deferred.promise(); + }, + + /** + Removes the collection locally. + */ + removeCollection: function(options) { + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'removeCollection', this.name, this.username, deferred); + + db.removeCollection(this.name, callbacks); + + return deferred.promise(); + }, + + /** + Removes all data in the collection and re-inits with same indexes (search fields, additional SFs) + */ + clear: function(options) { + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'clear', this.name, this.username, deferred); + + db.clear(this.name, callbacks); + + return deferred.promise(); + }, + + /** + Smart replace based on search fields + */ + change: function(data, options) { + + options = options || {}; + var lowerCaseReplaceCriteria = []; + + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'change', this.name, this.username, deferred); + + //addNew is false by default + if (check.isBoolean(options.addNew)) { + + callbacks.addNew = options.addNew; + + } else { + + callbacks.addNew = false; + } + + //markDirty is false by default + if (check.isBoolean(options.markDirty)) { + + callbacks.markDirty = options.markDirty; + + } else { + + callbacks.markDirty = false; + } + + //lowercase keys + if(check.isArray(options.replaceCriteria)) { + _.each(options.replaceCriteria, function(value) { + var newValue = value; + if(!check.isNumber(value) || !check.isBoolean(value)){ + newValue = value.toLowerCase(); + } + + lowerCaseReplaceCriteria.push(newValue); + }); + } else { + lowerCaseReplaceCriteria.push(options.replaceCriteria); + } + + //all fields indexed are used as the replace criteria by default + if (check.isArrayOfSAFields(lowerCaseReplaceCriteria, this.searchFields, this.additionalSearchFields)) { + + callbacks.replaceCriteria = options.replaceCriteria; + + } else if (check.hasJSONSearchFields(options.replaceCriteria)) { + setTimeout(function() { + callbacks.onFailure(constant.BAD_PARAMETER_WRONG_SEARCH_CRITERIA); + }, 0); + } + + if (check.isArrayOfObjects(data) || check.isEmptyArray(data)) { + db.change(this.name, data, callbacks); + } else { + setTimeout(function() { + callbacks.onFailure(constant.BAD_PARAMETER_EXPECTED_ARRAY_OF_OBJECTS); + }, 0); + } + + return deferred.promise(); + }, + + /** + Smart replace based on search fields + */ + markClean: function(documents, options) { + var deferred, + callbacks; + if (check.isArrayOfCleanDocuments(documents)) { + return db.markpushed(this.name, documents); + } else { + deferred = $.Deferred(); + callbacks = __generateCallbacks(options, 'markClean', this.name, this.username, deferred); + setTimeout(function() { + callbacks.onFailure(constant.BAD_PARAMETER_EXPECTED_ARRAY_OF_CLEAN_DOCUMENTS); + }, 0); + return deferred.promise(); + } + }, + + /** + Add a new function to a collection's protoype. + */ + enhance: function(name, func) { + + if (!check.isString(name)) { + + return 2; + + } else if (!check.isFunction(func)) { + + return 3; + + } else if (typeof JSONStoreInstance.prototype[name] !== 'function') { + + JSONStoreInstance.prototype[name] = func; + return 0; + + } else { + + return 14; + } + }, + + /** + Gets data defined in load portion of the adapter. + */ + load: function(options) { + + var deferred = $.Deferred(), + callbacks = __generateCallbacks(options, 'load', this.name, this.username, deferred), + collectionAdapter = this.adapter, + collectionName = this.name, + invocationData = {}, + resultData, + input, + myKey, + myPath, + resourceRequestSuccessCallback = function(response) { + + input = collectionAdapter.load.key.split('.'); + + if (input.length > 1) { + + myKey = input.pop(), + myPath = 'invocationResult.' + input.join('.'); + resultData = jspath.get(response, myKey, myPath); + + } else { + + resultData = [response.responseJSON[collectionAdapter.load.key]]; + } + + if (check.isObject(resultData, { + isArrayValid: true + }) && + resultData.length > 0 && !check.isUndefined(resultData[0])) { + + var arr = resultData[0]; + //Special case when the server returned no data via an empty array + if (typeof arr === 'object' && typeof arr.length !== 'undefined' && arr.length === 0) { + callbacks.onSuccess(constant.SUCCESS); //Returns via the success callback 0 docs loaded + } else { + + if (!check.isArray(arr)) { + arr = [arr]; + } + + db.store(collectionName, arr, callbacks); + } + + } else { + + callbacks.onFailure(constant.INVALID_KEY_IN_LOAD_OBJECT, response); + } + }, + resourceRequestFailureCallback = function(data) { + callbacks.onFailure(constant.FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER, data); + }; + + if (check.isValidAdapter(collectionAdapter) && + check.isValidLoadObject(collectionAdapter.load)) { + + invocationData = { + adapter: collectionAdapter.name, + procedure: collectionAdapter.load.procedure, + parameters: collectionAdapter.load.params + }; + + var resourceRequest = new WLResourceRequest('adapters/' + invocationData.adapter + '/' + invocationData.procedure, WLResourceRequest.GET); + resourceRequest.send().then( + resourceRequestSuccessCallback, + resourceRequestFailureCallback + ); + + } else { + callbacks.onFailure(constant.FAILED_TO_LOAD_INITIAL_DATA_FROM_ADAPTER_INVALID_LOAD_OBJ); + } + + return deferred.promise(); + }, + + /** + Prints the contents of the collection using WL.Logger.debug asynchronously. + */ + toString: function(limit, offset) { + + var col = this, + output = {}, + options = {}, + deferred = $.Deferred(); + + if (check.isUndefined(limit) && check.isUndefined(offset)) { + + col.findAll({ + limit: 100 + }).then(function(results) { + output = { + collection: col, + docs: results + }; + WL.Logger.ctx({ + pkg: constant.PKG_NAME + }).debug(output); + deferred.resolve(output); + }).fail(function(err) { + WL.Logger.ctx({ + pkg: constant.PKG_NAME + }).debug(err); + deferred.resolve(err); + }); + + } else if (check.isInt(limit) && limit === 0 && check.isUndefined(offset)) { + + setTimeout(function() { + output = { + collection: col + }; + output.collection.searchFields[WL.constant.ID_KEY] = 'number'; + WL.Logger.ctx({ + pkg: constant.PKG_NAME + }).debug(output); + deferred.resolve(output); + }, 0); + + } else { + + if (check.isInt(limit)) { + options.limit = limit; + } + + if (check.isInt(offset) && offset >= 0 && check.isInt(limit) && limit > 0) { + options.offset = offset; + } + + col.findAll(options) + + .then(function(results) { + output = { + collection: col, + docs: results + }; + WL.Logger.ctx({ + pkg: constant.PKG_NAME + }).debug(output); + deferred.resolve(output); + }) + + .fail(function(err) { + WL.Logger.ctx({ + pkg: constant.PKG_NAME + }).debug(err); + deferred.resolve(err); + }); + } + + return deferred.promise(); + } + + }; + + //public API + return { + init: _init, + get: _get, + initCollection: _initCollection, + usePassword: _usePassword, + clearPassword: _clearPassword, + closeAll: _closeAll, + documentify: _documentify, + changePassword: _changePassword, + destroy: _destroy, + getErrorMessage: _getErrorMessage, + startTransaction: _startTransaction, + commitTransaction: _commitTransaction, + rollbackTransaction: _rollbackTransaction, + fileInfo: _fileInfo, + QueryPart: QueryPart + }; + +}(WLJQ, WL_)); //WL.JSONStore diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/bootstrap.js b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/bootstrap.js new file mode 100644 index 000000000..2674312f3 --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/bootstrap.js @@ -0,0 +1,155 @@ +cordova.define("cordova-plugin-mfp.mfp", function(require, exports, module) { +/* + Licensed Materials - Property of IBM + + (C) Copyright 2015 IBM Corp. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +var mfpreadyfired = false; + +// {platform}/www/plugins/cordova-plugin-mfp/worklight +var WORKLIGHT_DIR = 'plugins/cordova-plugin-mfp/worklight'; + +// {platform}/www/plugins/cordova-plugin-mfp/static_app_props.js +var STATIC_APP_PROPS_PATH = WORKLIGHT_DIR + '/static_app_props.js'; + +// {platform}/www/plugins/cordova-plugin-mfp/static_app_props.js +var WLJQ_PATH = WORKLIGHT_DIR + '/wljq.js'; + +// {platform}/www/plugins/cordova-plugin-mfp/static_app_props.js +var WORKLIGHT_PATH = WORKLIGHT_DIR + '/worklight.js'; + +// {platform}/www/plugins/cordova-plugin-mfp/static_app_props.js +var WORKLIGHT_CHECKSUM_PATH = WORKLIGHT_DIR + '/checksum.js'; + +document.addEventListener('deviceready', loadMFP, false); + +function loadMFP(){ + if(typeof WL !== 'undefined' && WL.StaticAppProps){ + //console.log('Developer is injecting scripts manually'); + /* + + + + + + */ + mfpready(); + } else { + //console.log('Inject MFP Scripts dynamically'); + loadStaticAppProps(); + } + + function loadStaticAppProps(){ + //console.log("worklight/static_app_props.js"); + injectScript(findCordovaPath() + STATIC_APP_PROPS_PATH, loadJQ,bootError); + } + + function loadJQ(){ + //console.log("injecting script wljq.js"); + injectScript(findCordovaPath() + WLJQ_PATH, loadWorklight,bootError); + } + + function loadWorklight(){ + //console.log("injecting script worklight.js"); + injectScript(findCordovaPath() + WORKLIGHT_PATH, loadChecksum,bootError); + } + + function loadChecksum (){ + //console.log("injecting script checksum.js"); + injectScript(findCordovaPath() + WORKLIGHT_CHECKSUM_PATH, mfpready,bootError); + } + + function mfpready (){ + mfpFire(); + //call WL.Client.init unless user defined mfpClientCustomInit = true in config.xml, and propagated to static_app_props.js + if(WL.StaticAppProps && !WL.StaticAppProps.mfpClientCustomInit){ + console.log('Calling WL.Client.init(wlInitOptions);') + var options = typeof wlInitOptions !== 'undefined' ? wlInitOptions : {}; + WL.Client.init(options); + } else { + console.log('Developer will call WL.Client.init manually'); + } + //Inform developer they should load their own jquery and not use MFP internal version + deprecateWLJQ(); + } + + function mfpFire(){ + //console.log("bootstrap.js dispatching mfpjsloaded event"); + try { + var wlevent = new Event('mfpjsloaded'); + } + catch (e) { + if (e instanceof TypeError) { + // Trying to use old events + wlevent = document.createEvent('Event'); + wlevent.initEvent('mfpjsloaded', true, true); + } + else { + console.error(e.message); + } + } + // Dispatch the event. + document.dispatchEvent(wlevent); + mfpreadyfired = true; + } + + function deprecateWLJQ(){ + setTimeout(function checkWLJQ(){ + if(window.$ === WLJQ){ + console.error('Using WLJQ as your window.$ is deprecated, if needed, please load your own JQuery instance'); + } else if(window.jQuery === WLJQ){ + console.error('Using WLJQ as your window.jQuery is deprecated, if needed, please load your own JQuery instance'); + } + },10000); + } + + function injectScript(url, onload, onerror) { + var script = document.createElement("script"); + // onload fires even when script fails loads with an error. + script.onload = onload; + // onerror fires for malformed URLs. + script.onerror = onerror; + script.src = url; + document.head.appendChild(script); + } + + function bootError(){ + console.error("mfp bootstrap failed to inject script"); + } +} + +setTimeout(function mfpTimeOut(){ + if(!mfpreadyfired){ + loadMFP(); + } +},6000); + + +function findCordovaPath() { + var path = null; + var scripts = document.getElementsByTagName('script'); + var startterm = '/cordova.'; + var term = '/cordova.js'; + for (var n = scripts.length-1; n>-1; n--) { + var src = scripts[n].src.replace(/\?.*$/, ''); // Strip any query param (CB-6007). + // APAR 119091: findCordovaPath function to work with hashed builds. + var idx = src.indexOf(startterm); + if (idx >= 0 && src.substring(idx).replace(/cordova\.[^\.\/]*\.js/, "cordova.js") == term) { + term = src.substring(idx); + } + if (src.indexOf(term) === (src.length - term.length)) { + path = src.substring(0, src.length - term.length) + '/'; + break; + } + } + return path; +} + +}); diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/analytics/ibmmfpfanalytics.js b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/analytics/ibmmfpfanalytics.js new file mode 100644 index 000000000..9da1a6e67 --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/analytics/ibmmfpfanalytics.js @@ -0,0 +1,1696 @@ +/** + * @license + Licensed Materials - Property of IBM + + (C) Copyright 2015 IBM Corp. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD init. No dependencies + define(factory); + } else { + // Browser globals init + root.ibmmfpfanalytics = factory(); + + } +}(this, function () { + + /////////////////////////// logger + + REQ_SEND_LOGS = '/api/loguploader', + REQ_SEND_LOGS_RECEIVER = '/v1/loguploader', + REQ_UPDATE_CONFIG = '/api/clientLogProfile', + KEY_LOCAL_STORAGE_LOGS = '__WL_WEBLOG_LOGS__', + KEY_LOCAL_STORAGE_SWAP = '__WL_WEBLOG_SWAP__', + KEY_LOCAL_STORAGE_ANALYTICS = '__WL_WEBLOG_ANALYTICS__', + KEY_LOCAL_STORAGE_CONFIG = '__WL_WEBLOG_CONFIG__', + KEY_REMOTE_STORAGE_CONFIG = '__WL_WEBLOG_REMOTE_CONFIG__', + analyticsURLKey = 'com.worklight.oauth.analytics.url', + analyticsApiKey = 'com.worklight.oauth.analytics.api.key', + + + DEFAULT_MAX_STORAGE_SIZE = 500000, + BUFFER_TIME_IN_MILLISECONDS = 60000, + sendLogsTimeBuffer = 0, + + analyticsLocalStorage = { + appNamePrefix:'', + + init: function(appName) { + this.appNamePrefix=appName; + }, + + getItem: function(key) { + return localStorage.getItem(this.appNamePrefix + '.' + key); + }, + + setItem: function(key,value) { + localStorage.setItem(this.appNamePrefix + '.' + key, value); + }, + + removeItem: function(key) { + localStorage.removeItem(this.appNamePrefix + '.' + key); + } + }; + + var LEFT_BRACKET = '['; + var RIGHT_BRACKET = '] '; //There's a space at the end. + var _ANSALYTICS_PKG_NAME = 'wl.analytics'; + + var metadataHeader = {}; + var startupTime = 0; + var appSessionID = generateUUID('new'); + var userID = ''; + var clientId = ''; + var state = __getStateDefaults(); + var autoSendIntervalObject = "undefined"; + + // Private variables + var pendingTrackingIDs = {}; + var logger; + + if (!window.console) { // thanks a lot, IE9 + /*jshint -W020 */ + console = { + error: function() {}, + warn: function() {}, + info: function() {}, + log: function() {}, + debug: function() {}, + trace: function() {} + }; + } + + console.log = console.log || function() {}; // I suppose console.log is the most likely to exist. + console.warn = console.warn || console.log; + console.error = console.error || console.log; + console.info = console.info || console.log; + console.debug = console.debug || console.info; + console.trace = console.trace || console.debug; // try to keep the verbosity down a bit + + var priorities = { + trace : 600, + debug : 500, + log : 400, + info : 300, + warn : 200, + error : 100, + fatal : 50, + analytics : 25 + }; + + var __usingLocalConfiguration = function(){ + var configurationString = analyticsLocalStorage.getItem(KEY_REMOTE_STORAGE_CONFIG); + + if(configurationString == null){ + return true; + } + + return false; + }; + + + /* + * INIT - Load state if persisted. Else get default state + */ + (function(){ + + })(); + + function generateUUID(newSession) { + "use strict"; + + var d = new Date().getTime(); + var uuid = ''; + var generate = function(c) { + var r = (d + Math.random()*16)%16 | 0; + d = Math.floor(d/16); + return (c=='x' ? r : (r&0x3|0x8)).toString(16); + }; + if (newSession) { + uuid = 'xxxxxxxx-xxxx-4567-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, generate); + } + else { + uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, generate); + } + + return uuid; + }; + + var getTrackingId = function () { + return generateUUID(); + }; + + /* + * PRIVATE METHODS + */ + + var __send = function(keys) { + + + return new Promise(function (resolve, reject) { + + var data = getLogsData(keys); + + if(data == null || data == '' ){ + console.log('analytics: There are no persisted logs to send.'); + resolve('There were no persisted logs to send'); + return; + } + + var analyticsUrl = null; + try { + analyticsUrl = analyticsLocalStorage.getItem(analyticsURLKey); + }catch(e){ + // Do nothing + console.log('analytics: Failed to get analyticsUrl : ' + e.toString()); + } + + var sendlogSuccess = function(response){ + //logInboundForSendResponse(response[0]); + if(analyticsUrl != null && !analyticsUrl.includes('analytics-receiver')){ + emptyLogs(keys); + } + console.log('analytics: Client logs successfully sent to the server'); + resolve('Log was successfully sent'); + }; + + var sendLogError = function(err) { + console.error('analytics: Call failed, server returned: ' , (err ? err.statusText : "")); + reject('analytics: Call failed, server returned: ' + (err ? err.statusText : "")); + }; + + if(analyticsUrl != null && analyticsUrl.includes('analytics-receiver')){ + try { + reqUrl = analyticsUrl.replace(/['"]+/g, '') + REQ_SEND_LOGS_RECEIVER; + __receiverSendAjax(data, reqUrl, keys) + .then(sendlogSuccess,sendLogError); + }catch(e){ + console.error('analytics: Failed to log event due to exception: ' + e.toString()); + } + } else { + __ajax(data, REQ_SEND_LOGS) + .then(sendlogSuccess, sendLogError); + } + }); + }; + + var __receiverSendAjax = function(data,reqUrl,keys) { + + return new Promise(function (resolve, reject) { + var _options = {} ; + _options.scope = "receiver.mobileclient"; + var xhr = new WLResourceRequest(reqUrl, WLResourceRequest.POST, _options); + xhr.setHeader("Content-Type", "text/plain"); + + function onLogSendSuccess(transport) { + if(transport.status >= 200 && transport.status < 300){ + emptyLogs(keys); + //commented intentionally to avoid sending network metrics for loguploader + //logInboundResponse(xhr); + console.log('analytics: Call issued to ' + reqUrl); + resolve([xhr.networkMetadata, transport.responseText]); + } else { + reject({ + status: transport.status, + statusText: transport.responseText + }); + } + }; + + function onLogSendFailure(transport) { + analyticsLocalStorage.removeItem(analyticsURLKey); + reject({ + status: transport.errorCode, + statusText: transport.responseText + }); + }; + + function sendAnalyticsData() { + var track = logOutboundRequest(xhr); + if (track) { + var duration = new Date().getTime() - startupTime; + if (isNewSession()) { + logAnalyticsSessionStart(); + } + else if (duration > 1800000) { + logAnalyticsSessionStop(); + } + if (clientId != "") { + metadataHeader.clientID = clientId; + } + startupTime = new Date().getTime(); + xhr.setHeader("x-wl-analytics-tracking-id", xhr.trackingId); + xhr.setHeader("x-mfp-analytics-metadata", JSON.stringify(metadataHeader)); + } + xhr.send(data).then( + onLogSendSuccess, + onLogSendFailure + ); + }; + sendAnalyticsData(); + }); + }; + + var __ajax = function(data,path,method) { + + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + if (method == null){ + method = 'POST' + } + var reqUrl = metadataHeader.contextRoot + path; + xhr.open(method, metadataHeader.contextRoot+path,true); + + xhr.onload = function () { + if (this.status >= 200 && this.status < 300) { + resolve([xhr.networkMetadata,xhr.response]); + } else { + reject({ + status: this.status, + statusText: xhr.statusText + }); + } + }; + xhr.onerror = function () { + reject({ + status: this.status, + statusText: xhr.statusText + }); + }; + + function sendAnalyticsData() { + + if (!state.allDomains && (!metadataHeader.contextRoot || reqUrl.indexOf(metadataHeader.contextRoot) == -1)) { + xhr.send(data); + return; + } + + var oldOnReadyStateChange; + var track = logOutboundRequest(xhr); + if (track) { + var duration = new Date().getTime() - startupTime; + if (isNewSession()) { + logAnalyticsSessionStart(); + } + else if (duration > 1800000) { + logAnalyticsSessionStop(); + } + if (clientId != "") { + metadataHeader.clientID = clientId; + } + startupTime = new Date().getTime(); + xhr.setRequestHeader("x-wl-analytics-tracking-id", xhr.trackingId); + xhr.setRequestHeader("x-mfp-analytics-metadata", JSON.stringify(metadataHeader)); + } + + function onReadyStateChange() { + if (xhr.readyState == 4 /* complete */) { + /* This is where you can put code that you want to execute post-complete*/ + /* URL is kept in this._url */ + logInboundResponse(xhr); + console.log('analytics: Call issued to ' + reqUrl); + } + + if (oldOnReadyStateChange) { + oldOnReadyStateChange(); + } + } + + /* Set xhr.noIntercept to true to disable the interceptor for a particular call */ + if (!xhr.noIntercept) { + if (xhr.addEventListener) { + xhr.addEventListener("readystatechange", onReadyStateChange, false); + } else { + oldOnReadyStateChange = xhr.onreadystatechange; + xhr.onreadystatechange = onReadyStateChange; + } + } + + xhr.send(data); + }; + + sendAnalyticsData(); + + }); + + }; + + logOutboundRequest = function (request) { + try{ + if (!request.trackingId) { + request.trackingId = getTrackingId(); + } + else { + return false; + } + var outboundTimestamp = new Date().getTime(); + + var metadata = { +// '$path': url, //$path for legacy reasons + '$category' : 'network', + '$trackingid' : request.trackingId, + '$outboundTimestamp' : outboundTimestamp + }; + + var logMetadata = { + '$class':'wl.analytics.xhrInterceptor', + '$file':'ibmmfpfanalytics.js', + '$method':'intercept', +// '$line":138, + '$src':'javascript' + }; + + request.networkMetadata = metadata; + + var logData = { + 'pkg': 'wl.analytics', + 'timestamp': new Date().getTime(), + 'level': 'ANALYTICS', + 'msg': 'InternalRequestSender outbound', + 'metadata': logMetadata + }; + + __persistLog(logData, KEY_LOCAL_STORAGE_ANALYTICS); + return true; + + }catch(e){ + // Do nothing + } + }; + + + var logInboundResponse = function (request) { + + try{ + var trackingId = request.trackingId; + + if(trackingId){ + var inboundTimestamp = new Date().getTime(); + var numBytes = 0; + var responseText = '';//response.responseJSON;TODO + + /*jshint maxdepth:4*/ + if(responseText){ + numBytes = JSON.stringify(responseText).length; + } + + var metadata = request.networkMetadata; + + if(metadata !== null){ + var outboundTimestamp = metadata['$outboundTimestamp']; + var roundTripTime = inboundTimestamp - outboundTimestamp; + + metadata['$inboundTimestamp'] = inboundTimestamp; + metadata['$bytesReceived'] = numBytes; + metadata['$roundTripTime'] = roundTripTime; + metadata['$responseCode'] = request.status; + var method = null; + if(request.requestOptions != null){ + method = request.requestOptions.method; + } + if(method == null) { // Receiver: If request is WLResourceRequest + method = request.getMethod(); + } + metadata['$requestMethod'] = method; + metadata['$path'] = request.responseURL; + + request.networkMetadata = metadata; + + } + + + var logData = { + 'pkg': 'wl.analytics', + 'timestamp': new Date().getTime(), + 'level': 'ANALYTICS', + 'msg': 'InternalRequestSender logInboundResponse', + 'metadata': metadata + }; + __persistLog(logData, KEY_LOCAL_STORAGE_ANALYTICS); + } + }catch(e){ +// alert(e); + // Do nothing + } + }; + + + var logInboundForSendResponse = function (metadata) { + try{ + + var logData = { + 'pkg': 'wl.analytics', + 'timestamp': new Date().getTime(), + 'level': 'ANALYTICS', + 'msg': 'InternalRequestSender logInboundResponse', + 'metadata': metadata + }; + __persistLog(logData, KEY_LOCAL_STORAGE_ANALYTICS); + + }catch(e){ + console.error('analytics: Failed to log event'); + } + }; + + function getLogsData(keys){ + var persistedLogs = ''; + keys.forEach(function(key){ + var value = analyticsLocalStorage.getItem(key); + if(value !== null){ + if (persistedLogs !== ''){ + persistedLogs += ','; + } + persistedLogs += value; + } + }); + if (persistedLogs == ''){ + return ''; + } + + var logdata = { + __logdata : persistedLogs + }; + return JSON.stringify(logdata); + }; + + var __persistLog = function(log, key){ + if(__fileSizeReached(key)){ + if(key === KEY_LOCAL_STORAGE_LOGS){ + __attemptFileSwap(); + }else{ + // No swapping for analytics + return; + } + } + + var stringified = JSON.stringify(log); + var persistedLogs = analyticsLocalStorage.getItem(key); + + if(persistedLogs === null){ + persistedLogs = stringified; + }else{ + persistedLogs += ', ' + stringified; + } + + try{ + analyticsLocalStorage.setItem(key, persistedLogs); + }catch(e){ + console.log('analytics: Local storage capacity reached. Client logs will not be persisted'); + } + }; + + var __attemptFileSwap = function(){ + try{ + var currentLogs = analyticsLocalStorage.getItem(KEY_LOCAL_STORAGE_LOGS); + analyticsLocalStorage.setItem(KEY_LOCAL_STORAGE_SWAP, currentLogs); + analyticsLocalStorage.removeItem(KEY_LOCAL_STORAGE_LOGS); + }catch(e){ + console.log('analytics: Local storage capacity reached. WL.Logger will delete old logs to make room for new ones.'); + analyticsLocalStorage.removeItem(KEY_LOCAL_STORAGE_LOGS); + analyticsLocalStorage.removeItem(KEY_LOCAL_STORAGE_SWAP); + } + }; + + var __processUpdateConfig = function(configString){ + var config = null; + try{ + config = JSON.parse(configString); + }catch(e){ + + } + if(config && config.clientLogProfileConfig){ + console.log('analytics: Matching configuration successfully retrieved from the server.'); + var wllogger = config.clientLogProfileConfig; + analyticsLocalStorage.setItem(KEY_REMOTE_STORAGE_CONFIG, analyticsLocalStorage.getItem(KEY_LOCAL_STORAGE_CONFIG)); + state.levelFromServer = null; + state.filtersFromServer = null; + __setServerOverrides(wllogger.clientLogProfiles); + }else{ + console.log('analytics: No matching configurations found from the server. Defaulting to local configuration'); + analyticsLocalStorage.removeItem(KEY_REMOTE_STORAGE_CONFIG); + + var configurationString = analyticsLocalStorage.getItem(KEY_LOCAL_STORAGE_CONFIG); + var configuration = JSON.parse(configurationString); + __updateState(configuration); + __unsetServerOverrides(); + } + }; + + /* + * UTILITY METHODS + */ + + function printStackTrace(e) { + e = e || { + guess: true + }; + var t = e.e || null, + n = !!e.guess; + var r = new printStackTrace.implementation, + i = r.run(t); + return n ? r.guessAnonymousFunctions(i) : i + } + if (typeof module !== "undefined" && module.exports) { + module.exports = printStackTrace + } + printStackTrace.implementation = function() {}; + printStackTrace.implementation.prototype = { + run: function(e, t) { + e = e || this.createException(); + t = t || this.mode(e); + if (t === "other") { + return this.other(arguments.callee) + } else { + return this[t](e) + } + }, + createException: function() { + try { + this.undef() + } catch (e) { + return e + } + }, + mode: function(e) { + if (e["arguments"] && e.stack) { + return "chrome" + } else if (e.stack && e.sourceURL) { + return "safari" + } else if (e.stack && e.number) { + return "ie" + } else if (typeof e.message === "string" && typeof window !== "undefined" && window.opera) { + if (!e.stacktrace) { + return "opera9" + } + if (e.message.indexOf("\n") > -1 && e.message.split("\n").length > e.stacktrace.split("\n").length) { + return "opera9" + } + if (!e.stack) { + return "opera10a" + } + if (e.stacktrace.indexOf("called from line") < 0) { + return "opera10b" + } + return "opera11" + } else if (e.stack) { + return "firefox" + } + return "other" + }, + instrumentFunction: function(e, t, n) { + e = e || window; + var r = e[t]; + e[t] = function() { + n.call(this, printStackTrace().slice(4)); + return e[t]._instrumented.apply(this, arguments) + }; + e[t]._instrumented = r + }, + deinstrumentFunction: function(e, t) { + if (e[t].constructor === Function && e[t]._instrumented && e[t]._instrumented.constructor === Function) { + e[t] = e[t]._instrumented + } + }, + chrome: function(e) { + var t = (e.stack + "\n").replace(/^\S[^\(]+?[\n$]/gm, "").replace(/^\s+(at eval )?at\s+/gm, "").replace(/^([^\(]+?)([\n$])/gm, "{anonymous}()@$1$2").replace(/^Object.\s*\(([^\)]+)\)/gm, "{anonymous}()@$1").split("\n"); + t.pop(); + return t + }, + safari: function(e) { + return e.stack.replace(/\[native code\]\n/m, "").replace(/^(?=\w+Error\:).*$\n/m, "").replace(/^@/gm, "{anonymous}()@").split("\n") + }, + ie: function(e) { + var t = /^.*at (\w+) \(([^\)]+)\)$/gm; + return e.stack.replace(/at Anonymous function /gm, "{anonymous}()@").replace(/^(?=\w+Error\:).*$\n/m, "").replace(t, "$1@$2").split("\n") + }, + firefox: function(e) { + return e.stack.replace(/(?:\n@:0)?\s+$/m, "").replace(/^[\(@]/gm, "{anonymous}()@").split("\n") + }, + opera11: function(e) { + var t = "{anonymous}", + n = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/; + var r = e.stacktrace.split("\n"), + i = []; + for (var s = 0, o = r.length; s < o; s += 2) { + var u = n.exec(r[s]); + if (u) { + var a = u[4] + ":" + u[1] + ":" + u[2]; + var f = u[3] || "global code"; + f = f.replace(//, "$1").replace(//, t); + i.push(f + "@" + a + " -- " + r[s + 1].replace(/^\s+/, "")) + } + } + return i + }, + opera10b: function(e) { + var t = /^(.*)@(.+):(\d+)$/; + var n = e.stacktrace.split("\n"), + r = []; + for (var i = 0, s = n.length; i < s; i++) { + var o = t.exec(n[i]); + if (o) { + var u = o[1] ? o[1] + "()" : "global code"; + r.push(u + "@" + o[2] + ":" + o[3]) + } + } + return r + }, + opera10a: function(e) { + var t = "{anonymous}", + n = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i; + var r = e.stacktrace.split("\n"), + i = []; + for (var s = 0, o = r.length; s < o; s += 2) { + var u = n.exec(r[s]); + if (u) { + var a = u[3] || t; + i.push(a + "()@" + u[2] + ":" + u[1] + " -- " + r[s + 1].replace(/^\s+/, "")) + } + } + return i + }, + opera9: function(e) { + var t = "{anonymous}", + n = /Line (\d+).*script (?:in )?(\S+)/i; + var r = e.message.split("\n"), + i = []; + for (var s = 2, o = r.length; s < o; s += 2) { + var u = n.exec(r[s]); + if (u) { + i.push(t + "()@" + u[2] + ":" + u[1] + " -- " + r[s + 1].replace(/^\s+/, "")) + } + } + return i + }, + other: function(e) { + var t = "{anonymous}", + n = /function\s*([\w\-$]+)?\s*\(/i, + r = [], + i, s, o = 10; + while (e && e["arguments"] && r.length < o) { + i = n.test(e.toString()) ? RegExp.$1 || t : t; + s = Array.prototype.slice.call(e["arguments"] || []); + r[r.length] = i + "(" + this.stringifyArguments(s) + ")"; + e = e.caller + } + return r + }, + stringifyArguments: function(e) { + var t = []; + var n = Array.prototype.slice; + for (var r = 0; r < e.length; ++r) { + var i = e[r]; + if (i === undefined) { + t[r] = "undefined" + } else if (i === null) { + t[r] = "null" + } else if (i.constructor) { + if (i.constructor === Array) { + if (i.length < 3) { + t[r] = "[" + this.stringifyArguments(i) + "]" + } else { + t[r] = "[" + this.stringifyArguments(n.call(i, 0, 1)) + "..." + this.stringifyArguments(n.call(i, -1)) + "]" + } + } else if (i.constructor === Object) { + t[r] = "#object" + } else if (i.constructor === Function) { + t[r] = "#function" + } else if (i.constructor === String) { + t[r] = '"' + i + '"' + } else if (i.constructor === Number) { + t[r] = i + } + } + } + return t.join(",") + }, + sourceCache: {}, + ajax: function(e) { + var t = this.createXMLHTTPObject(); + if (t) { + try { + t.open("GET", e, false); + t.send(null); + return t.responseText + } catch (n) {} + } + return "" + }, + createXMLHTTPObject: function() { + var e, t = [function() { + return new XMLHttpRequest + }, function() { + return new ActiveXObject("Msxml2.XMLHTTP") + }, function() { + return new ActiveXObject("Msxml3.XMLHTTP") + }, function() { + return new ActiveXObject("Microsoft.XMLHTTP") + }]; + for (var n = 0; n < t.length; n++) { + try { + e = t[n](); + this.createXMLHTTPObject = t[n]; + return e + } catch (r) {} + } + }, + isSameDomain: function(e) { + return typeof location !== "undefined" && e.indexOf(location.hostname) !== -1 + }, + getSource: function(e) { + if (!(e in this.sourceCache)) { + this.sourceCache[e] = this.ajax(e).split("\n") + } + return this.sourceCache[e] + }, + guessAnonymousFunctions: function(e) { + for (var t = 0; t < e.length; ++t) { + var n = /\{anonymous\}\(.*\)@(.*)/, + r = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/, + i = e[t], + s = n.exec(i); + if (s) { + var o = r.exec(s[1]); + if (o) { + var u = o[1], + a = o[2], + f = o[3] || 0; + if (u && this.isSameDomain(u) && a) { + var l = this.guessAnonymousFunction(u, a, f); + e[t] = i.replace("{anonymous}", l) + } + } + } + } + return e + }, + guessAnonymousFunction: function(e, t, n) { + var r; + try { + r = this.findFunctionName(this.getSource(e), t) + } catch (i) { + r = "getSource failed with url: " + e + ", exception: " + i.toString() + } + return r + }, + findFunctionName: function(e, t) { + var n = /function\s+([^(]*?)\s*\(([^)]*)\)/; + var r = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/; + var i = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/; + var s = "", + o, u = Math.min(t, 20), + a, f; + for (var l = 0; l < u; ++l) { + o = e[t - l - 1]; + f = o.indexOf("//"); + if (f >= 0) { + o = o.substr(0, f) + } + if (o) { + s = o + s; + a = r.exec(s); + if (a && a[1]) { + return a[1] + } + a = n.exec(s); + if (a && a[1]) { + return a[1] + } + a = i.exec(s); + if (a && a[1]) { + return a[1] + } + } + } + return "(?)" + } + }; + + function __fileSizeReached(key){ + var persistedLogs = analyticsLocalStorage.getItem(key); + if(persistedLogs === null) { + return false; + } + + var m = encodeURIComponent(persistedLogs).match(/%[89ABab]/g); + var size = persistedLogs.length + (m ? m.length : 0); + + var maxSize = __state().maxFileSize; + if(maxSize === null || typeof maxSize === 'undefined') { + maxSize = DEFAULT_MAX_STORAGE_SIZE; + } + + if(size >= maxSize){ + return true; + } + + return false; + }; + + function __getStateDefaults() { + var udf; // because undefined can be overridden + return { + enabled : true, + stringify : true, + pretty: false, + stacktrace : false, + ismsie : !!(document.all && document.querySelector && !document.addEventListener), + callback : '', + tag : {level: false, pkg: true}, + pkg : '', + filters : udf, + filtersFromServer: udf, + level : 'trace', + levelFromServer : udf, + metadata : {}, + capture : udf, + captureFromServer : udf, + analyticsCapture : udf, + allDomains : udf, + maxFileSize : udf, + autoSendLogs: true, + autoSendLogsInterval: 60 + }; + }; + + function __resetState() { + state = __getStateDefaults(); + analyticsLocalStorage.removeItem('__WL_WEBLOG_LOGS__'); + analyticsLocalStorage.removeItem('__WL_WEBLOG_ANALYTICS__'); + analyticsLocalStorage.removeItem('__WL_WEBLOG_CONFIG__'); + return this; + }; + + function __getLogArgArray(args, priority, pkg) { + + var msgStr = __stringifyArguments(args); + var caller = getCallerLine(); + var originMeta = { + '$src': 'js' + }; + if(!state.metadata.hasOwnProperty('filename') && caller != ""){ + var parsed = formatStackLine(caller); + originMeta = { + '$class' : 'Object', + '$file' : parsed.file, + '$method' : parsed.method, + '$line' : parsed.linenumber, + '$src': 'js' + }; + } + var meta = __extend({},true, state.metadata ,originMeta); //clone obj + state.metadata = {}; //clear metadata obj + + for (var i = 0; i < args.length; i++) { + + if (args[i] instanceof Error) { + args[i] = {'$name': args[i].toString(), '$stacktrace': printStackTrace({e: args[i]})}; + } + } + + if (typeof priority === 'string') { + priority = priority.toUpperCase(); + } + + return [priority, pkg, msgStr, meta, (new Date()).getTime()]; + }; + + + function getCallerLine(){ + var stack = printStackTrace(); + for(var i = 1; i 0 && + !__insideArray(currentPriority, stateLevel) + ); + + } else if (typeof stateLevel === 'string') { + stateLevel = stateLevel.toLowerCase();//Handle WARN, wArN, etc instead of just warn + return (//Get numeric value and compare current with state + typeof (priorities[currentPriority]) === 'number' && + typeof (priorities[stateLevel]) === 'number' && + (priorities[currentPriority] > priorities[stateLevel]) + ); + + } else if (typeof stateLevel === 'number') { + + return (//Compare current with state + typeof (priorities[currentPriority]) === 'number' && + (priorities[currentPriority] > stateLevel) + ); + } + + return true; //Bail out, level is some unknown type + }; + + function __checkLoggingLevel(priority, pkg) { + var currFilters = state.filtersFromServer || state.filters; + if (__getKeys(currFilters).length > 0) { // non-empty filters object + return __checkLevel(priority, __getCurrentPackageFilterLevel(pkg)); + }else{ + return __checkLevel(priority, state.levelFromServer || state.level); + } + return false; + }; + + function __getCurrentPackageFilterLevel(pkg){ + var configFilters = state.filtersFromServer || state.filters; + if (pkg == null){ + pkg = ''; + } + for (var i in configFilters) { + if (configFilters[i].name === pkg){ + return configFilters[i].level ; + } + } + return null; + }; + + function __log(args, priority) { + + priority = priority.toLowerCase(); + //TODO check if env is IE and then set console.trace = console.debug; + state = __state(); + var str = '', + pkg = state.pkg; + + //state.pkg = ''; //clear pkg from state obj + + if (!state.enabled || + __checkLoggingLevel(priority, pkg)) { + state.metadata = {}; //clear metadata obj + return; + } + + if (state.stringify) { + str = __stringifyArguments(args); + } + + //Apply Package Tag + if (state.tag.pkg && typeof pkg === 'string' && pkg.length > 0) { + str = LEFT_BRACKET + pkg + RIGHT_BRACKET + str; + } + + //Apply Level Tag + if (state.tag.level) { + str = LEFT_BRACKET + priority.toUpperCase() + RIGHT_BRACKET + str; + } + + if (!state.stringify && str.length > 0) { + args.unshift(str); + } + + // Queue for later sending + var logArgArray = __getLogArgArray(args, priority, pkg) + var state = __state(); + + //setTimeout(function () { + if (typeof(Storage) !== 'undefined') { + var level = logArgArray[0]; + var pkg = logArgArray[1]; + var msg = logArgArray[2]; + var meta = logArgArray[3]; + if(clientId != ""){ + meta["$clientId"] = clientId; + } + var time = logArgArray[4]; + + var logData = { + 'timestamp': time, + 'level': level, + 'pkg': pkg, + 'msg': msg, + 'metadata': meta + }; + + if(level === 'ANALYTICS' && state.analyticsCapture !== false){ + __persistLog(logData, KEY_LOCAL_STORAGE_ANALYTICS); + }else if(state.capture !== false){ + __persistLog(logData, KEY_LOCAL_STORAGE_LOGS); + } + } + //}, 0, logArgArray); + + //Log to the console + // we use WL.StaticAppProps instead of WL.Client.getEnvironment because the former is + // guaranteed to be available + if (typeof console === 'object') { // avoid infinite loop on Adobe AIR + + if (typeof console[priority] === 'function') { + (state.stringify) ? console[priority](str) : console[priority].apply(console, args); + + } else if (priority === 'fatal') { + if (typeof console.error === 'function') { + (state.stringify) ? console.error(str) : console.error.apply(console, args); + } + + } else if (priority === 'trace') { + if (typeof console.debug === 'function') { + (state.stringify) ? console.debug(str) : console.debug.apply(console, args); + } + + } else if (priority === 'analytics') { + // Do nothing + } else if (typeof console.log === 'function') { + (state.stringify) ? console.log(str) : console.log.apply(console, args); + + } else if (state.ismsie && typeof console.log === 'object') { + (state.stringify) ? console.log(str) : console.log.apply(console, args); + } + + } + + //The default value of state.callback is an empty string (not a function) + if (typeof state.callback === 'function') { + if (!state.stringify) { + str = args; + } + state.callback(str, priority, pkg); + } + + }; + + function _pkg(pkgName) { + + if (state.pkg != null){ + state.pkg = pkgName; + } + return this; + }; + + function _config(options) { + __setState(__extend({},options || {})); + return this; + }; + + function _ctx(options) { + __setState(__extend({},options || {})); + return this; + }; + + function _create(options) { + __setState(__extend({},options || {})); + return this; + }; + + function _sendLoggerLog() { + return __send([KEY_LOCAL_STORAGE_ANALYTICS,KEY_LOCAL_STORAGE_LOGS, KEY_LOCAL_STORAGE_SWAP]); + }; + + function _metadata(obj) { + + if (typeof obj === 'object') { + state.metadata = obj; + } + + return this; + }; + + function __sendAll(checkAutoSend) { + if(typeof(checkAutoSend) === "boolean"){ + if(checkAutoSend === true){ + return _processAutomaticTrigger(); + } + } + return __send([KEY_LOCAL_STORAGE_ANALYTICS,KEY_LOCAL_STORAGE_LOGS, KEY_LOCAL_STORAGE_SWAP]); + }; + + function _updateConfigFromServer() { + + return new Promise(function (resolve, reject) { + var appName = metadataHeader.mfpAppName ; + var platform = metadataHeader.os ; + var getConfigUrl = REQ_UPDATE_CONFIG + '/' + appName + '/' + platform; + __ajax({}, getConfigUrl,'GET') + .then(function (metadata) { + __processUpdateConfig(metadata[1]); + resolve(metadata[1]); + }) + .catch(function (err) { + console.error('analytics: Failed to call the server', err.statusText); + reject('analytics: Failed to call the server' + err.statusText) + }); + + }); + }; + + function __setServerOverrides(configFilters,level,capture) { + var updatedConfigFilters = null; + for (i = 0; i < configFilters.length; i++) { + if (configFilters[i].name == null || configFilters[i].name == ''){ + level = configFilters[i].level; + }else{ + if (updatedConfigFilters == null){ + updatedConfigFilters = [configFilters[i]]; + }else{ + updatedConfigFilters.push(configFilters[i]); + } + } + } + _config({levelFromServer: level, captureFromServer: capture, filtersFromServer: updatedConfigFilters}); + }; + + function logAnalyticsCrash(errorEvt) { + var duration = new Date().getTime() - startupTime; + var filename = errorEvt.filename; + var linenumber = errorEvt.lineno; + var errorMessage = errorEvt.message; + var method = 'none'; + var stack = []; + if(errorEvt.error != null){ + var errstack = errorEvt.error.stack; + stack = errstack.split('\n'); + } + var caller = ""; + for(var i = 1; i -1) + }; + + function emptyLogs(keys){ + keys.forEach(function(key){ + analyticsLocalStorage.removeItem(key); + }); + }; + + function initErrorHandler(){ + if(window.hasErrorHandler == null){ + window.addEventListener('error', function (evt) { + logAnalyticsCrash(evt); + }); + window.hasErrorHandler = true; + } + }; + + function browserName(){ + var browserName = navigator.appName; + var nVer = navigator.appVersion; + var nAgt = navigator.userAgent; + var nameOffset,verOffset,ix; + + // In Opera, the true version is after "Opera" or after "Version" + if ((verOffset=nAgt.indexOf("Opera"))!=-1) { + browserName = "Opera"; + } + // In MSIE, the true version is after "MSIE" in userAgent + else if ((verOffset=nAgt.indexOf("MSIE"))!=-1) { + browserName = "Microsoft Internet Explorer"; + } + // In Chrome, the true version is after "Chrome" + else if ((verOffset=nAgt.indexOf("Chrome"))!=-1) { + browserName = "Chrome"; + } + // In Safari, the true version is after "Safari" or after "Version" + else if ((verOffset=nAgt.indexOf("Safari"))!=-1) { + browserName = "Safari"; + } + // In Firefox, the true version is after "Firefox" + else if ((verOffset=nAgt.indexOf("Firefox"))!=-1) { + browserName = "Firefox"; + } + // In most other browsers, "name/version" is at the end of userAgent + else if ( (nameOffset=nAgt.lastIndexOf(' ')+1) < (verOffset=nAgt.lastIndexOf('/')) ) { + browserName = nAgt.substring(nameOffset,verOffset); + } + + return browserName; + } + + + function _init(deviceID, appName, contextRoot){ + startupTime = new Date().getTime(); + initErrorHandler(); + metadataHeader.contextRoot = "/mfp"; + if (contextRoot != null && contextRoot != ''){ + metadataHeader.contextRoot = contextRoot; + } + metadataHeader.deviceID = "Undefined"; + if (deviceID != null && deviceID != ''){ + metadataHeader.deviceID = deviceID; + } + metadataHeader.mfpAppVersion = "latest"; + + metadataHeader.mfpAppName = "MFPWebApp"; + if (appName != null && appName != ''){ + metadataHeader.mfpAppName = appName; + } + + analyticsLocalStorage.init(metadataHeader.mfpAppName); + try { + if (typeof(Storage) !== 'undefined') { + + var configurationString = null; + + if(__usingLocalConfiguration()){ + configurationString = analyticsLocalStorage.getItem(KEY_LOCAL_STORAGE_CONFIG); + }else{ + configurationString = analyticsLocalStorage.getItem(KEY_REMOTE_STORAGE_CONFIG); + } + + if (configurationString === null){ + var state = __state(); + state.maxFileSize = DEFAULT_MAX_STORAGE_SIZE; + __updateState(state); + + var stateString = JSON.stringify(state); + analyticsLocalStorage.setItem(KEY_LOCAL_STORAGE_CONFIG, stateString); + } else { + var configuration = JSON.parse(configurationString); + __updateState(configuration); + } + } + } catch ( err ) { + return console.error(err.message); + } + + if (userID == '') { + userID = metadataHeader.deviceID; + } + metadataHeader.os = "web"; // MFP + metadataHeader.osVersion = navigator.platform; // human-readable o/s version; like "MacIntel" + metadataHeader.brand = navigator.appVersion; // human-readable brand; + metadataHeader.model = browserName(); // human-readable model; like "Chrome" + metadataHeader.appVersionDisplay = metadataHeader.mfpAppVersion; // human readable display version + metadataHeader.appVersionCode = metadataHeader.mfpAppVersion; // version as known to the app store + metadataHeader.appStoreId = metadataHeader.mfpAppName; // app pkg name (e.g. com.ibm.MyApp) + metadataHeader.appStoreLabel = metadataHeader.mfpAppName; + + if ( autoSendIntervalObject !== "undefined" || autoSendIntervalObject != null ) { + clearInterval(autoSendIntervalObject); + } + _triggerAutoSendLog(); + }; + + function __unsetServerOverrides() { + var udf; // undefined + state.levelFromServer = udf; + state.captureFromServer = udf; + state.filtersFromServer = udf; + _config({levelFromServer: udf, captureFromServer: udf, filtersFromServer: udf}); + }; + + // For web logger state manipulation + function __state() { + return state; + }; + + function __updateState(newState) { + if(newState) { + state = newState; + } + }; + + function capture(captureLogs) { + _config({capture: typeof(captureLogs) === "boolean" ? captureLogs : true}); + }; + + function __enable(enableLogs) { + _config({enabled: typeof(enableLogs) === "boolean" ? enableLogs : true}); + }; + + function __extend(){ + for(var i=1; i BUFFER_TIME_IN_MILLISECONDS){ + if(autoSend){ + __sendAll(); + } + + sendLogsTimeBuffer = currentTime; + } + } + + var _triggerAutoSendLog = function(){ + var autoSend =__state().autoSendLogs; + var interval = __state().autoSendLogsInterval; + if (autoSendIntervalObject != null){ + clearInterval(autoSendIntervalObject); + } + if(autoSend){ + autoSendIntervalObject = setInterval(function() { + __sendAll(); + }, interval*1000); + } + } + + function _enableAutoSend(autoSend) { + _config({autoSendLogs: typeof(autoSend) === "boolean" ? autoSend : true}); + }; + + function _enableAutoSend(autoSend, interval) { + _config({autoSendLogs: typeof(autoSend) === "boolean" ? autoSend : true, autoSendLogsInterval: typeof(interval) === "number" ? interval : 60}); + }; + + var _setClientId = function (newClientId) { + clientId = newClientId; + }; + + var _triggerFeedbackMode = function () { + alert("Inapp Feedback is not supported in web platform"); + }; + + //public API + return { + init: _init, + enable : _enable, + disable : _disable, + state: _state, + send: __sendAll, + setUserContext: _setUserContext, + addEvent: _event, + logger: logger, + log: _customLog, + enableAutoSend: _enableAutoSend, + triggerFeedbackMode: _triggerFeedbackMode, + _config:_config, + _setClientId:_setClientId + } + +})); diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/de/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/de/messages.json new file mode 100644 index 000000000..2a0cbd1ca --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/de/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "PII user facing" , +"accessDenied" : "Zugriff verweigert", +"authFailure" : "Beim Verarbeiten der Anforderung von der Anwendung wurde ein Fehler festgestellt.", +"applicationDenied" : "Anwendung inaktiviert", +"browserIsNotSupported" : "{0} wird momentan nicht unterstützt.", +"cancel" : "Abbrechen", +"close" : "Schließen", +"cookiesAreDisabled" : "In Ihrem Browser sind Cookies inaktiviert. Aktivieren Sie Cookies, damit die Anwendung ordnungsgemäß funktioniert.", +"copyToClipboard" : "Kopieren", +"details" : "Details", +"diagApp" : "App-Diagnose", +"diagTime" : "Zeit", +"diagApplicationName" : "Anwendungsname", +"diagApplicationVersion" : "Anwendungsversion", +"diagServiceURL" : "Service-URL", +"diagDevicePlatform" : "Geräteplattform", +"diagDeviceVersion" : "Geräteversion", +"diagScreenResolution" : "Bildschirmauflösung", +"diagAirplaneMode" : "Flugzeugmodus", +"diagUsingNetwork" : "Verwendetes Netz", +"diagWifiName" : "WiFi-Name", +"diagMobileNetworkType" : "Typ des mobilen Netzes", +"diagCarrierName" : "Name des Betreibers", +"diagErrorCode" : "Fehlercode", +"diagErrorMessage" : "Fehlernachricht", +"diagHttpStatus" : "HTTP-Status", +"diagIPAddress" : "IP-Adresse", +"directUpdateNotificationTitle" : "Aktualisierung verfügbar", +"directUpdateNotificationMessage" : "Es stehen neuere Webressourcen zur Verfügung. Für Aktualisierung bestätigen (Dateigröße: {0} MB).", +"directUpdateNotificationMessageKilobytes" : "Es stehen neuere Webressourcen zur Verfügung. Für Aktualisierung bestätigen (Dateigröße: {0} KB).", +"directUpdateErrorTitle" : "Aktualisierung fehlgeschlagen", +"directUpdateErrorMessage" : "Die direkte Aktualisierung ist fehlgeschlagen.", +"directUpdateErrorMessageNotEnoughStorage" : "Für die Anwendung steht ein Update zur Verfügung, aber es ist nicht genügend Speicherplatz auf den Gerät vorhanden (erforderlicher Speicherplatz: {0} MB, verfügbarer Speicherplatz: {1} MB).", +"directUpdateErrorMessageFailedDownloadingZipFile" : "Die Datei mit der Anwendungsaktualisierung konnte nicht heruntergeladen werden.", +"directUpdateErrorMessageFailedProcessingZipFile" : "Die Datei mit der Anwendungsaktualisierung konnte nicht verarbeitet werden.", +"downloadAppWebResourcesPleaseSpecifyAppID" : "Die App-Ressourcen können nicht heruntergeladen werden. Geben Sie in der Anzeige mit den Einstellungen die App-ID an.", +"downloadAppWebResourcesAppIdNotExist" : "Die Anwendung '{0}' wurde nicht gefunden. Implementieren Sie sie zunächst im Server der Plattform IBM MobileFirst.", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "Die App-Ressourcen können nicht heruntergeladen werden. Geben Sie in der Anzeige mit den Einstellungen die App-Version an.", +"downloadAppWebResourcesSkinIsNotValid" : "Die App-Ressourcen können nicht heruntergeladen werden. Die Oberfläche {0} ist nicht vorhanden. Stellen Sie sicher, dass getSkinName() eine gültige Oberfläche ergibt.", +"downloadAppWebResourcesAppVersionNotExist" : "Anwendung '{0}' {1} für {2} nicht gefunden", +"deviceAuthenticationFail" : "Konnektivitätsfehler", +"saveCertificateFailure" : "Das Zertifikat kann nicht gespeichert werden.", +"downloadAppWebResourcesConnectionToServerUnavailable" : "Es ist keine Verbindung zum Server verfügbar. Die Anwendungsressourcen können nicht heruntergeladen werden.", +"expandWindow" : "Anwendung zur Benutzung einblenden", +"exit" : "Beenden", +"exitApplication" : "Anwendung beenden", +"error" : "Fehler", +"gadgetUpdateAvailable" : "Anwendungsaktualisierung verfügbar", +"getNewVersion" : "Neue Version abrufen", +"handleTimeOut" : "Zeitlimitüberschreitung bei der Anforderung für {0}. Stellen Sie sicher, dass die Hostadresse für die Anwendung verfügbar ist (besonders für Android- und iPhone-Apps).", +"invalidUsernamePassword" : "Ungültiger Benutzername oder ungültiges Kennwort", +"keepAliveInBackgroundText" : "Anwendung wird im Hintergrund weiter ausgeführt", +"loading" : "Ladevorgang", +"login" : "Anmeldung", +"minimize" : "Symbolgröße", +"missingFeatureException" : "{1} konnte nicht aufgerufen werden, weil {0} in der Anwendung fehlt. Fügen Sie {0} zum Anwendungsdeskriptor hinzu. Erstellen und implementieren Sie dann die Anwendung neu.", +"name" : "Name:", +"noInternet" : "Keine Verbindung zum Service verfügbar", +"notificationTitle" : "Servicebenachrichtigung", +"notAvailable" : "Nicht verfügbar", +"ok" : "OK", +"password" : "Kennwort:", +"reload" : "Erneut laden", +"restore" : "Wiederherstellen", +"requestTimeout" : "Die Anwendung konnte keine Verbindung zu dem Service herstellen.", +"responseNotRecognized" : "Nicht erwartete Antwort", +"settings" : "Einstellungen", +"serverError" : "Fehler beim Prozeduraufruf", +"tryAgain" : "Erneut versuchen", +"userInstanceAccessViolationException" : "Sie versuchen, sich bei einer nicht für Sie registrierten Anwendung anzumelden.", +"unexpectedError" : "Der Server konnte die Anfrage von der Anwendung nicht verarbeiten. Versuchen Sie es später erneut.", +"unresponsiveHost" : "Der Service ist zurzeit nicht verfügbar.", +"update" : "Aktualisieren", +"upgrade" : "Upgrade", +"upgradeGadget" : "Die Version Ihrer Anwendung ist {0}. Version {1} dieser Anwendung ist verfügbar. Klicken Sie zum Herunterladen und Installieren auf OK.", +"wlclientInitFailure" : "Fehler", +"wlSettings" : "Einstellungen der Plattform IBM MobileFirst", +"userEnrollmentUnsupportedOS" : "Fehler bei der Authentifizierung des Benutzerzertifikats. Nicht unterstützte Clientplattform.", +"failureCallingMethod" : "Fehler beim Aufrufen von {0}", +"challengeHandlingCanceled" : "Die Abfrage-Handler-Operation wurde abgebrochen.", +"unsupportedEnvironment" : "Nicht unterstützte Umgebung", +"redirect" : "Umleiten" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/es/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/es/messages.json new file mode 100644 index 000000000..195e7d735 --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/es/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "Para el usuario de PII" , +"accessDenied" : "Acceso denegado", +"authFailure" : "Se ha producido un error al procesar la solicitud desde la aplicación.", +"applicationDenied" : "Aplicación inhabilitada", +"browserIsNotSupported" : "{0} no está actualmente soportado.", +"cancel" : "Cancelar", +"close" : "Cerrar", +"cookiesAreDisabled" : "Las cookies están actualmente inhabilitadas en el navegador. Debe habilitarlas para que la aplicación funcione correctamente.", +"copyToClipboard" : "Copiar", +"details" : "Detalles", +"diagApp" : "Diagnóstico de aplicación", +"diagTime" : "Hora", +"diagApplicationName" : "Nombre de aplicación", +"diagApplicationVersion" : "Versión de aplicación", +"diagServiceURL" : "URL de servicio", +"diagDevicePlatform" : "Plataforma de dispositivo", +"diagDeviceVersion" : "Versión de dispositivo", +"diagScreenResolution" : "Resolución de pantalla", +"diagAirplaneMode" : "Modo avión", +"diagUsingNetwork" : "Uso de red", +"diagWifiName" : "Nombre de WiFi", +"diagMobileNetworkType" : "Tipo de red móvil", +"diagCarrierName" : "Nombre de operadora", +"diagErrorCode" : "Código de error", +"diagErrorMessage" : "Mensaje de error", +"diagHttpStatus" : "Estado HTTP", +"diagIPAddress" : "Dirección IP", +"directUpdateNotificationTitle" : "Actualización disponible", +"directUpdateNotificationMessage" : "Hay disponibles nuevos recursos web. Confirme para actualizar. (tamaño archivos: {0} MB).", +"directUpdateNotificationMessageKilobytes" : "Hay disponibles nuevos recursos web. Confirme para actualizar. (tamaño archivos: {0} KB).", +"directUpdateErrorTitle" : "Actualización fallida", +"directUpdateErrorMessage" : "Anomalía en Direct Update. ", +"directUpdateErrorMessageNotEnoughStorage" : "Hay una actualización disponible para la aplicación, pero no hay suficiente espacio disponible en el dispositivo (espacio necesario: {0} MB, espacio disponible: {1} MB).", +"directUpdateErrorMessageFailedDownloadingZipFile" : "Ha fallado la descarga del archivo de actualización de aplicación.", +"directUpdateErrorMessageFailedProcessingZipFile" : "Ha fallado el proceso del archivo de actualización de la aplicación.", +"downloadAppWebResourcesPleaseSpecifyAppID" : "No se pueden descargar los recursos de la aplicación. Especifique la ID de la aplicación en la pantalla Configuración.", +"downloadAppWebResourcesAppIdNotExist" : "La aplicación '{0}' no se puede encontrar. Despliéguela en primer lugar en IBM MobileFirst Platform Server.", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "No se pueden descargar los recursos de la aplicación. Especifique la Versión de la aplicación en la pantalla Configuración.", +"downloadAppWebResourcesSkinIsNotValid" : "No se pueden descargar los recursos de la aplicación. El skin: {0} no existe. Asegúrese de que getSkinName() se resuelve en un skin válido.", +"downloadAppWebResourcesAppVersionNotExist" : "No se puede encontrar la aplicación '{0}' {1} para {2}", +"deviceAuthenticationFail" : "Error de conectividad", +"saveCertificateFailure" : "No se ha podido guardar el certificado", +"downloadAppWebResourcesConnectionToServerUnavailable" : "No está disponible una conexión al servidor. No se pueden descargar los recursos de la aplicación.", +"expandWindow" : "Expanda la aplicación para utilizarla", +"exit" : "Salir", +"exitApplication" : "Salir de la aplicación", +"error" : "Error", +"gadgetUpdateAvailable" : "Actualización de la aplicación disponible", +"getNewVersion" : "Obtener una versión nueva", +"handleTimeOut" : "Se ha excedido el tiempo de espera de la solicitud de {0}. Asegúrese de que la dirección del host está disponible en la aplicación (especialmente relevante para aplicaciones Android e iPhone).", +"invalidUsernamePassword" : "Nombre de usuario o contraseña no válido", +"keepAliveInBackgroundText" : "La aplicación continúa en ejecución en segundo plano", +"loading" : "Cargando", +"login" : "Iniciar sesión", +"minimize" : "Minimizar", +"missingFeatureException" : "No se ha podido llamar a {1} porque falta {0} en la aplicación. Añada {0} al descriptor de la aplicación, vuelva a crearlo y despliéguelo.", +"name" : "Nombre:", +"noInternet" : "La conexión al servicio no está disponible.", +"notificationTitle" : "Notificación de servicio", +"notAvailable" : "No disponible", +"ok" : "Aceptar", +"password" : "Contraseña:", +"reload" : "Volver a cargar", +"restore" : "Restaurar", +"requestTimeout" : "La aplicación no se ha podido conectar al servicio.", +"responseNotRecognized" : "Respuesta inesperada.", +"settings" : "Valores", +"serverError" : "Error de invocación de procedimiento.", +"tryAgain" : "Inténtelo de nuevo", +"userInstanceAccessViolationException" : "Está intentando iniciar sesión en una aplicación que no está registrada para el usuario.", +"unexpectedError" : "El servidor no ha podido procesar la solicitud desde la aplicación. Inténtelo de nuevo más tarde.", +"unresponsiveHost" : "El servicio no está disponible en este momento.", +"update" : "Actualizar", +"upgrade" : "Actualizar", +"upgradeGadget" : "La versión de la aplicación es {0}. La versión {1} de esta aplicación está disponible. Pulse Aceptar para descargarla e instalarla.", +"wlclientInitFailure" : "Error", +"wlSettings" : "Configuración de IBM MobileFirst Platform", +"userEnrollmentUnsupportedOS" : "Error de autenticación de certificado de usuario: plataforma de cliente no soportada.", +"failureCallingMethod" : "Error de llamada {0}", +"challengeHandlingCanceled" : "Se ha cancelado la operación del manejador de desafíos.", +"unsupportedEnvironment" : "Entorno no soportado", +"redirect" : "Redireccionar" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/fr/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/fr/messages.json new file mode 100644 index 000000000..eab200c8e --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/fr/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "PII user facing" , +"accessDenied" : "Accès refusé", +"authFailure" : "Une erreur est survenue lors du traitement de la demande de l'application.", +"applicationDenied" : "Application désactivée", +"browserIsNotSupported" : "{0} n'est pas pris en charge actuellement.", +"cancel" : "Annuler", +"close" : "Fermer", +"cookiesAreDisabled" : "Les cookies sont désactivés dans votre navigateur. Vous devez les activer pour que l'application fonctionne correctement.", +"copyToClipboard" : "Copier", +"details" : "Détails", +"diagApp" : "Diagnostics d'application", +"diagTime" : "Heure", +"diagApplicationName" : "Nom de l'application", +"diagApplicationVersion" : "Version de l'application", +"diagServiceURL" : "Adresse URL du service", +"diagDevicePlatform" : "Plateforme du terminal", +"diagDeviceVersion" : "Version du terminal", +"diagScreenResolution" : "Résolution d'écran", +"diagAirplaneMode" : "Mode avion", +"diagUsingNetwork" : "Utilisation du réseau", +"diagWifiName" : "Nom du Wi-Fi", +"diagMobileNetworkType" : "Type de réseau de mobile", +"diagCarrierName" : "Nom de l'opérateur", +"diagErrorCode" : "Code d'erreur", +"diagErrorMessage" : "Message d'erreur", +"diagHttpStatus" : "Statut HTTP", +"diagIPAddress" : "Adresse IP", +"directUpdateNotificationTitle" : "Mise à jour disponible", +"directUpdateNotificationMessage" : "Des ressources Web plus récentes sont disponibles. Confirmez la mise à jour. (taille du fichier : {0} Mo).", +"directUpdateNotificationMessageKilobytes" : "Des ressources Web plus récentes sont disponibles. Confirmez la mise à jour. (taille du fichier : {0} Ko).", +"directUpdateErrorTitle" : "Echec de la mise à jour", +"directUpdateErrorMessage" : "Echec de la mise à jour directe.", +"directUpdateErrorMessageNotEnoughStorage" : "Une mise à jour de l'application est disponible mais l'espace disponible sur le terminal est insuffisant (taille requise : {0} Mo, espace disponible : {1} Mo).", +"directUpdateErrorMessageFailedDownloadingZipFile" : "Echec du téléchargement du fichier de mise à jour de l'application.", +"directUpdateErrorMessageFailedProcessingZipFile" : "Echec du traitement du fichier de mise à jour de l'application.", +"downloadAppWebResourcesPleaseSpecifyAppID" : "Impossible de télécharger les ressources de l'application. Spécifiez l'ID de l'application dans l'écran Paramètres.", +"downloadAppWebResourcesAppIdNotExist" : "L'application '{0}' est introuvable. Déployez-la d'abord sur le serveur IBM MobileFirst Platform.", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "Impossible de télécharger les ressources de l'application. Spécifiez la version de l'application dans l'écran Paramètres.", +"downloadAppWebResourcesSkinIsNotValid" : "Impossible de télécharger les ressources de l'application. L'habillage {0} n'existe pas. Assurez-vous que getSkinName() génère un habillage valide.", +"downloadAppWebResourcesAppVersionNotExist" : "Application '{0}' {1} introuvable pour {2}", +"deviceAuthenticationFail" : "Erreur de connectivité", +"saveCertificateFailure" : "Impossible de sauvegarder le certificat", +"downloadAppWebResourcesConnectionToServerUnavailable" : "La connexion au serveur n'est pas disponible. Impossible de télécharger les ressources d'application.", +"expandWindow" : "Développez l'application pour l'utiliser", +"exit" : "Quitter", +"exitApplication" : "Quitter l'application", +"error" : "Erreur", +"gadgetUpdateAvailable" : "Mise à jour de l'application disponible", +"getNewVersion" : "Obtenir la nouvelle version", +"handleTimeOut" : "La demande a expiré pour {0}. Assurez-vous que l'adresse de l'hôte est disponible dans l'application (notamment pour les applications Android et iPhone).", +"invalidUsernamePassword" : "Nom d'utilisateur ou mot de passe non valide", +"keepAliveInBackgroundText" : "L'application continue de s'exécuter en arrière-plan", +"loading" : "Chargement", +"login" : "Connexion", +"minimize" : "Réduire", +"missingFeatureException" : "Echec de l'appel de {1} car {0} manque dans l'application. Ajoutez {0} au descripteur d'application, régénérez-le et déployez-le.", +"name" : "Nom\u00A0:", +"noInternet" : "La connexion au service n'est pas disponible.", +"notificationTitle" : "Notification de service", +"notAvailable" : "Non disponible", +"ok" : "OK", +"password" : "Mot de passe\u00A0:", +"reload" : "Recharger", +"restore" : "Restaurer", +"requestTimeout" : "L'application n'est pas parvenue à se connecter au service.", +"responseNotRecognized" : "Réponse inattendue.", +"settings" : "Paramètres", +"serverError" : "Erreur d'invocation de procédure.", +"tryAgain" : "Réessayer", +"userInstanceAccessViolationException" : "Vous essayez de vous connecter à une application qui n'est pas enregistrée pour vous.", +"unexpectedError" : "Le serveur n'est pas parvenu à traiter la demande de l'application. Essayez à nouveau ultérieurement.", +"unresponsiveHost" : "Le service n'est pas disponible actuellement.", +"update" : "Mettre à jour", +"upgrade" : "Mettre à niveau", +"upgradeGadget" : "La version de votre application est {0}. La version {1} de cette application est disponible. Cliquez sur OK pour la télécharger et l'installer.", +"wlclientInitFailure" : "Erreur", +"wlSettings" : "Paramètres d'IBM MobileFirst Platform", +"userEnrollmentUnsupportedOS" : "Echec de l'authentification du certificat utilisateur\u00A0: plateforme client non prise en charge.", +"failureCallingMethod" : "Echec de l'appel de {0}", +"challengeHandlingCanceled" : "L'opération du gestionnaire de demandes d'authentification a été annulée.", +"unsupportedEnvironment" : "Environnement non pris en charge", +"redirect" : "Redirection" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/he/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/he/messages.json new file mode 100644 index 000000000..6650cc218 --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/he/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "PII user facing" , +"accessDenied" : "הגישה חסומה", +"authFailure" : "אירעה שגיאה בעיבוד הבקשה מהיישום.", +"applicationDenied" : "היישום מושבת", +"browserIsNotSupported" : "{0} אינו נתמך כרגע.", +"cancel" : "ביטול", +"close" : "סגירה", +"cookiesAreDisabled" : "קובצי Cookie מושבתית כרגע בדפדפן שלכם. עליכם להפעיל אותם כדי שהיישום יפעל כהלכה.", +"copyToClipboard" : "העתקה", +"details" : "פרטים", +"diagApp" : "אבחון יישום", +"diagTime" : "שעה", +"diagApplicationName" : "שם יישום", +"diagApplicationVersion" : "גרסת יישום", +"diagServiceURL" : "URL שירות", +"diagDevicePlatform" : "פלטפורמת התקן", +"diagDeviceVersion" : "גרסת התקן", +"diagScreenResolution" : "רזולוציית מסך", +"diagAirplaneMode" : "מצב מטוס", +"diagUsingNetwork" : "שימוש ברשת", +"diagWifiName" : "שם WiFi", +"diagMobileNetworkType" : "סוג רשת ניידת", +"diagCarrierName" : "שם ספק", +"diagErrorCode" : "קוד שגיאה", +"diagErrorMessage" : "הודעת שגיאה", +"diagHttpStatus" : "מצב HTTP", +"diagIPAddress" : "כתובת IP", +"directUpdateNotificationTitle" : "יש עדכון זמין", +"directUpdateNotificationMessage" : "זמינים משאבי רשת חדשים יותר. אשרו כדי לעדכן. (גודל הקובץ הוא {0} ‏MB).‏", +"directUpdateNotificationMessageKilobytes" : "זמינים משאבי רשת חדשים יותר. אשרו כדי לעדכן. (גודל הקובץ הוא {0} ‏KB).‏", +"directUpdateErrorTitle" : "העדכון נכשל", +"directUpdateErrorMessage" : "כשל בעדכון הישיר.", +"directUpdateErrorMessageNotEnoughStorage" : "זמין עדכון עבור היישום, אך אין מספיק מקום זמין בהתקן (גודל דרוש: {0} MB,‏ שטח זמין: {1} MB‏).", +"directUpdateErrorMessageFailedDownloadingZipFile" : "כשל בהורדת קובץ העדכון של היישום.", +"directUpdateErrorMessageFailedProcessingZipFile" : "כשל בעיבוד קובץ העדכון של היישום.", +"downloadAppWebResourcesPleaseSpecifyAppID" : "לא ניתן להוריד את משאבי היישום. ציינו את זיהוי היישום במסך ההגדרות.", +"downloadAppWebResourcesAppIdNotExist" : "היישום '{0}' לא נמצא .‏ הציבו אותו תחילה בשרת IBM MobileFirst Platform.‏", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "לא ניתן להוריד את משאבי היישום. ציינו את גרסת היישום במסך ההגדרות.", +"downloadAppWebResourcesSkinIsNotValid" : "לא ניתן להוריד את משאבי היישום. המעטפת: {0} אינה קיימת. ודאי כי הפונקציה getSkinName()‎ מתפענחת למעטפת חוקית.", +"downloadAppWebResourcesAppVersionNotExist" : "היישום '{0}' {1} עבור {2} לא נמצא", +"deviceAuthenticationFail" : "שגיאת קישוריות", +"saveCertificateFailure" : "לא ניתן לשמור את האישור", +"downloadAppWebResourcesConnectionToServerUnavailable" : "חיבור לשרת אינו זמין. לא ניתן להוריד את משאבי היישום.", +"expandWindow" : "הרחיבו את היישום כדי להשתמש בו", +"exit" : "יציאה", +"exitApplication" : "יציאה מהיישום", +"error" : "שגיאה", +"gadgetUpdateAvailable" : "זמין עדכון של היישום", +"getNewVersion" : "קבלת גרסה חדשה", +"handleTimeOut" : "הבקשה חרגה ממגבלת הזמן עבור {0}. ודאו שכתובת המארח זמינה ליישום (רלוונטי בעיקר עבור יישומי Android ו-iPhone).‏", +"invalidUsernamePassword" : "שם משתמש או סיסמה לא חוקיים", +"keepAliveInBackgroundText" : "היישום ממשיך לרוץ ברקע", +"loading" : "טעינה", +"login" : "התחברות", +"minimize" : "מזעור", +"missingFeatureException" : "Failed to call {1} because {0} is missing in the application.‎ Add {0} to the application descriptor, rebuild and deploy it.‎", +"name" : "שם:", +"noInternet" : "אין חיבור זמין לשירות זה.", +"notificationTitle" : "הודעת שירות", +"notAvailable" : "לא זמין", +"ok" : "אישור", +"password" : "סיסמה:", +"reload" : "טעינה מחדש", +"restore" : "שחזור", +"requestTimeout" : "היישום לא הצליח להתחבר לשירות.", +"responseNotRecognized" : "תגובה לא צפויה.", +"settings" : "הגדרות", +"serverError" : "שגיאה בהפעלת פרוצדורה.", +"tryAgain" : "נסיון חוזר", +"userInstanceAccessViolationException" : "אתם מנסים להתחבר ליישום שאינו רשום עבורכם.", +"unexpectedError" : "השרת לא הצליח לעבד את הבקשה מהיישום. נא לנסות שוב מאוחר יותר.", +"unresponsiveHost" : "השירות אינו זמין כרגע.", +"update" : "עדכון", +"upgrade" : "שידרוג", +"upgradeGadget" : "גרסת היישום שלכם היא {0}. גרסה {1} של יישום זה זמינה כעת. לחצו על 'אישור' כדי להוריד ולהתקין אותה.", +"wlclientInitFailure" : "שגיאה", +"wlSettings" : "הגדרות IBM MobileFirst Platform", +"userEnrollmentUnsupportedOS" : "כשל באימות אישור משתמש: פלטפורמת לקוח לא נתמכת.", +"failureCallingMethod" : "כשל בקריאה למתודה {0}", +"challengeHandlingCanceled" : "Challenge handler operation was cancelled.‎", +"unsupportedEnvironment" : "סביבה לא נתמכת", +"redirect" : "ניתוב" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/it/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/it/messages.json new file mode 100644 index 000000000..27c97eb94 --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/it/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "PII user facing" , +"accessDenied" : "Accesso negato", +"authFailure" : "È stato rilevato un errore durante l'elaborazione della richiesta dall'applicazione", +"applicationDenied" : "Applicazione disabilitata", +"browserIsNotSupported" : "{0} non è attualmente supportato.", +"cancel" : "Annulla", +"close" : "Chiudi", +"cookiesAreDisabled" : "Attualmente i cookie sono disabilitati nel browser in uso. È necessario abilitarli per un corretto funzionamento dell'applicazione.", +"copyToClipboard" : "Copia", +"details" : "Dettagli", +"diagApp" : "Diagnostica app", +"diagTime" : "Ora", +"diagApplicationName" : "Nome applicazione", +"diagApplicationVersion" : "Versione applicazione", +"diagServiceURL" : "URL servizio", +"diagDevicePlatform" : "Piattaforma dispositivo", +"diagDeviceVersion" : "Versione dispositivo", +"diagScreenResolution" : "Risoluzione schermo", +"diagAirplaneMode" : "Modalità aereo", +"diagUsingNetwork" : "Utilizzo della rete", +"diagWifiName" : "Nome WiFi", +"diagMobileNetworkType" : "Tipo di rete mobile", +"diagCarrierName" : "Nome vettore", +"diagErrorCode" : "Codice di errore", +"diagErrorMessage" : "Messaggio di errore", +"diagHttpStatus" : "Stato HTTP", +"diagIPAddress" : "Indirizzo IP", +"directUpdateNotificationTitle" : "Aggiornamento disponibile", +"directUpdateNotificationMessage" : "Sono disponibili nuove risorse web. Confermare l'aggiornamento (la dimensione file è {0} MB).", +"directUpdateNotificationMessageKilobytes" : "Sono disponibili nuove risorse web. Confermare l'aggiornamento (la dimensione file è {0} KB).", +"directUpdateErrorTitle" : "Aggiornamento non riuscito", +"directUpdateErrorMessage" : "Errore dell'aggiornamento diretto.", +"directUpdateErrorMessageNotEnoughStorage" : "È disponibile un aggiornamento per l'applicazione, ma lo spazio disponibile sul dispositivo non è sufficiente (dimensione richiesta: {0} MB, spazio disponibile: {1} MB).", +"directUpdateErrorMessageFailedDownloadingZipFile" : "Download del file di aggiornamento dell'applicazione non riuscito.", +"directUpdateErrorMessageFailedProcessingZipFile" : "Elaborazione del file di aggiornamento dell'applicazione non riuscita.", +"downloadAppWebResourcesPleaseSpecifyAppID" : "Impossibile scaricare le risorse app. Specificare l'ID app nella schermata Impostazioni.", +"downloadAppWebResourcesAppIdNotExist" : "Impossibile trovare l'applicazione '{0}'. Eseguire prima la distribuzione su IBM MobileFirst Platform Server.", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "Impossibile scaricare le risorse app. Specificare la versione app nella schermata Impostazioni.", +"downloadAppWebResourcesSkinIsNotValid" : "Impossibile scaricare le risorse app. skin: {0} non esistente. Assicurarsi che getSkinName() sia risolto in uno skin valido.", +"downloadAppWebResourcesAppVersionNotExist" : "Impossibile trovare l'applicazione '{0}' {1} per {2}", +"deviceAuthenticationFail" : "Errore di connettività", +"saveCertificateFailure" : "Impossibile salvare il certificato", +"downloadAppWebResourcesConnectionToServerUnavailable" : "Una connessione al server non è disponibile. Impossibile scaricare le risorse dell'applicazione.", +"expandWindow" : "Espandere l'applicazione per utilizzarla", +"exit" : "Esci", +"exitApplication" : "Esci dall'applicazione", +"error" : "Errore", +"gadgetUpdateAvailable" : "Aggiornamento dell'applicazione disponibile", +"getNewVersion" : "Ottieni nuova versione", +"handleTimeOut" : "Richiesta scaduta per {0}. Assicurarsi che l'indirizzo host sia disponibile per l'applicazione (particolarmente importante per le app di Android e iPhone).", +"invalidUsernamePassword" : "Nome utente o password non validi", +"keepAliveInBackgroundText" : "L'esecuzione dell'applicazione prosegue in background", +"loading" : "Caricamento", +"login" : "Accesso", +"minimize" : "Riduci al minimo", +"missingFeatureException" : "Chiamata a {1} non riuscita perché {0} non è presente nell'applicazione. Aggiungere {0} al descrittore dell'applicazione, crearla nuovamente e distribuirla.", +"name" : "Nome:", +"noInternet" : "La connessione al servizio non è disponibile.", +"notificationTitle" : "Notifica del servizio", +"notAvailable" : "Non disponibile", +"ok" : "OK", +"password" : "Password:", +"reload" : "Ricarica", +"restore" : "Ripristina", +"requestTimeout" : "L'applicazione non è riuscita ad effettuare la connessione al servizio.", +"responseNotRecognized" : "Risposta non prevista.", +"settings" : "Impostazioni", +"serverError" : "Errore di richiamo procedura.", +"tryAgain" : "Riprova", +"userInstanceAccessViolationException" : "Si sta cercando di collegarsi a un'applicazione a cui non si è registrati.", +"unexpectedError" : "Il server non è riuscito a elaborare la richiesta dall'applicazione. Riprovare successivamente.", +"unresponsiveHost" : "Il servizio attualmente non è disponibile.", +"update" : "Aggiorna", +"upgrade" : "Esegui l'upgrade", +"upgradeGadget" : "La versione dell'applicazione è {0}. È disponibile la versione {1} di questa applicazione. Fare clic su OK per scaricare e installare.", +"wlclientInitFailure" : "Errore", +"wlSettings" : "Impostazioni di IBM MobileFirst Platform", +"userEnrollmentUnsupportedOS" : "Errore di autenticazione del certificato utente: piattaforma client non supportata.", +"failureCallingMethod" : "Errore durante il richiamo di {0}", +"challengeHandlingCanceled" : "Operazione Challenge Handler annullata.", +"unsupportedEnvironment" : "Ambiente non supportato", +"redirect" : "Reindirizza" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/ja/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/ja/messages.json new file mode 100644 index 000000000..13ad6589f --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/ja/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "PII ユーザー・インターフェース" , +"accessDenied" : "アクセスは拒否されました", +"authFailure" : "アプリケーションからの要求の処理中にエラーが発生しました。", +"applicationDenied" : "アプリケーションは使用できません", +"browserIsNotSupported" : "{0} は現在サポートされていません。", +"cancel" : "キャンセル", +"close" : "閉じる", +"cookiesAreDisabled" : "Cookie は現在ご使用のブラウザーで無効になっています。 これらを有効にしてアプリケーションが適切に機能するようにしてください。", +"copyToClipboard" : "コピー", +"details" : "詳細", +"diagApp" : "アプリケーション診断", +"diagTime" : "時刻", +"diagApplicationName" : "アプリケーション名", +"diagApplicationVersion" : "アプリケーション・バージョン", +"diagServiceURL" : "サービス URL", +"diagDevicePlatform" : "デバイス・プラットフォーム", +"diagDeviceVersion" : "デバイス・バージョン", +"diagScreenResolution" : "画面解像度", +"diagAirplaneMode" : "機内モード", +"diagUsingNetwork" : "ネットワークの使用", +"diagWifiName" : "WiFi 名", +"diagMobileNetworkType" : "モバイル・ネットワーク・タイプ", +"diagCarrierName" : "通信事業者名", +"diagErrorCode" : "エラー・コード", +"diagErrorMessage" : "エラー・メッセージ", +"diagHttpStatus" : "HTTP 状況", +"diagIPAddress" : "IP アドレス", +"directUpdateNotificationTitle" : "更新が利用可能", +"directUpdateNotificationMessage" : "より新しい Web リソースが利用可能です。 確認して更新してください。 (ファイル・サイズ {0} MB)", +"directUpdateNotificationMessageKilobytes" : "より新しい Web リソースが利用可能です。 確認して更新してください。 (ファイル・サイズ {0} KB)", +"directUpdateErrorTitle" : "更新が失敗", +"directUpdateErrorMessage" : "直接の更新が失敗しました。", +"directUpdateErrorMessageNotEnoughStorage" : "アプリケーションの更新が利用可能ですが、デバイス上に十分な使用可能スペースがありません (必要なサイズ: {0} MB、使用可能スペース: {1} MB)。", +"directUpdateErrorMessageFailedDownloadingZipFile" : "アプリケーション更新ファイルのダウンロードが失敗しました。", +"directUpdateErrorMessageFailedProcessingZipFile" : "アプリケーション更新ファイルの処理が失敗しました。", +"downloadAppWebResourcesPleaseSpecifyAppID" : "アプリケーション・リソースをダウンロードできません。 「設定」画面の「アプリケーション ID」を指定してください。", +"downloadAppWebResourcesAppIdNotExist" : "アプリケーション '{0}' が見つかりません。 最初にそれを IBM MobileFirst Platform Server にデプロイしてください。", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "アプリケーション・リソースをダウンロードできません。 「設定」画面の「アプリケーション・バージョン」を指定してください。", +"downloadAppWebResourcesSkinIsNotValid" : "アプリケーション・リソースをダウンロードできません。 スキン: {0} は存在しません。 getSkinName() の値が確実に有効なスキンになるようにしてください。", +"downloadAppWebResourcesAppVersionNotExist" : "アプリケーション '{0}' {1} ({2} 用) が見つかりません", +"deviceAuthenticationFail" : "接続エラー", +"saveCertificateFailure" : "証明書を保存できません", +"downloadAppWebResourcesConnectionToServerUnavailable" : "サーバーに接続できません。 アプリケーション・リソースをダウンロードできません。", +"expandWindow" : "アプリケーションを展開して使用してください", +"exit" : "終了", +"exitApplication" : "アプリケーションの終了", +"error" : "エラー", +"gadgetUpdateAvailable" : "アプリケーションの更新が利用可能です", +"getNewVersion" : "新バージョンの入手", +"handleTimeOut" : "{0} の要求がタイムアウトになりました。 ホスト・アドレスがアプリケーション (特に Android や iPhone のアプリケーション) で使用できることを確認してください。", +"invalidUsernamePassword" : "無効なユーザー名またはパスワード", +"keepAliveInBackgroundText" : "アプリケーションはバックグラウンドで引き続き稼働中", +"loading" : "ロード中", +"login" : "ログイン", +"minimize" : "最小化", +"missingFeatureException" : "アプリケーションに {0} がないため {1} の呼び出しに失敗しました。 アプリケーション記述子に {0} を追加し、再ビルドしデプロイしてください。", +"name" : "名前:", +"noInternet" : "サービスに接続できません。", +"notificationTitle" : "サービス通知", +"notAvailable" : "使用不可", +"ok" : "OK", +"password" : "パスワード:", +"reload" : "再ロード", +"restore" : "復元", +"requestTimeout" : "アプリケーションがサービスへの接続に失敗しました。", +"responseNotRecognized" : "予期しない応答。", +"settings" : "設定", +"serverError" : "プロシージャー呼び出しエラー。", +"tryAgain" : "やり直してください", +"userInstanceAccessViolationException" : "登録されていないアプリケーションにログインしようとしています。", +"unexpectedError" : "サーバーはアプリケーションからの要求を処理できませんでした。 後でもう一度やり直してください。", +"unresponsiveHost" : "サービスは現在使用できません。", +"update" : "更新", +"upgrade" : "アップグレード", +"upgradeGadget" : "ご使用のアプリケーションのバージョンは {0} です。 このアプリケーションのバージョン {1} が使用可能です。 これをダウンロードしてインストールするには「OK」をクリックしてください。", +"wlclientInitFailure" : "エラー", +"wlSettings" : "IBM MobileFirst Platform の設定", +"userEnrollmentUnsupportedOS" : "ユーザー証明書認証障害: サポートされていないクライアント・プラットフォーム。", +"failureCallingMethod" : "障害呼び出し {0}", +"challengeHandlingCanceled" : "Challenge handler の操作がキャンセルされました。", +"unsupportedEnvironment" : "非サポート環境", +"redirect" : "リダイレクト" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/ko/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/ko/messages.json new file mode 100644 index 000000000..68bde778f --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/ko/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "PII 사용자 지향" , +"accessDenied" : "액세스 거부됨", +"authFailure" : "애플리케이션의 요청을 처리하는 중에 오류가 발생했습니다.", +"applicationDenied" : "애플리케이션 사용 안함", +"browserIsNotSupported" : "{0}은(는) 현재 지원되지 않습니다.", +"cancel" : "취소", +"close" : "닫기", +"cookiesAreDisabled" : "쿠키는 브라우저에서 현재 사용되지 않습니다. 애플리케이션이 제대로 작동하려면 쿠키를 사용해야 합니다.", +"copyToClipboard" : "복사", +"details" : "세부사항", +"diagApp" : "애플리케이션 진단", +"diagTime" : "시간", +"diagApplicationName" : "애플리케이션 이름", +"diagApplicationVersion" : "애플리케이션 버전", +"diagServiceURL" : "서비스 URL", +"diagDevicePlatform" : "디바이스 플랫폼", +"diagDeviceVersion" : "디바이스 버전", +"diagScreenResolution" : "화면 해상도", +"diagAirplaneMode" : "통신제한 모드", +"diagUsingNetwork" : "사용 중인 네트워크", +"diagWifiName" : "WiFi 이름", +"diagMobileNetworkType" : "모바일 네트워크 유형", +"diagCarrierName" : "통신회사 이름", +"diagErrorCode" : "오류 코드", +"diagErrorMessage" : "오류 메시지", +"diagHttpStatus" : "HTTP 상태", +"diagIPAddress" : "IP 주소", +"directUpdateNotificationTitle" : "업데이트 사용 가능", +"directUpdateNotificationMessage" : "최신 웹 자원이 사용 가능합니다. 업데이트 확인(파일 크기: {0}MB).", +"directUpdateNotificationMessageKilobytes" : "최신 웹 자원이 사용 가능합니다. 업데이트 확인(파일 크기: {0}KB).", +"directUpdateErrorTitle" : "업데이트 실패", +"directUpdateErrorMessage" : "직접 업데이트에 실패했습니다.", +"directUpdateErrorMessageNotEnoughStorage" : "애플리케이션에 대한 업데이트를 사용할 수 있지만 디바이스의 사용 가능한 공간이 충분하지 않습니다(필수 크기: {0}MB, 사용 가능한 공간: {1} MB).", +"directUpdateErrorMessageFailedDownloadingZipFile" : "애플리케이션 업데이트 파일을 다운로드하는 데 실패했습니다.", +"directUpdateErrorMessageFailedProcessingZipFile" : "애플리케이션 업데이트 파일을 처리하는 데 실패했습니다.", +"downloadAppWebResourcesPleaseSpecifyAppID" : "애플리케이션 자원을 다운로드할 수 없습니다. 설정 화면에서 애플리케이션 ID를 지정하십시오.", +"downloadAppWebResourcesAppIdNotExist" : "'{0}' 애플리케이션을 찾을 수 없습니다. 먼저 IBM MobileFirst Platform Server에 해당 애플리케이션을 배치하십시오.", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "애플리케이션 자원을 다운로드할 수 없습니다. 설정 화면에서 애플리케이션 버전을 지정하십시오.", +"downloadAppWebResourcesSkinIsNotValid" : "애플리케이션 자원을 다운로드할 수 없습니다. {0} 스킨이 존재하지 않습니다. getSkinName()이 올바른 스킨에 해석되는지 확인하십시오.", +"downloadAppWebResourcesAppVersionNotExist" : "{2}용 '{0}' {1} 애플리케이션을 찾을 수 없습니다. ", +"deviceAuthenticationFail" : "연결성 오류", +"saveCertificateFailure" : "인증서를 저장할 수 없습니다.", +"downloadAppWebResourcesConnectionToServerUnavailable" : "서버에 연결할 수 없습니다. 애플리케이션 자원을 다운로드할 수 없습니다.", +"expandWindow" : "사용할 애플리케이션 펼치기", +"exit" : "종료", +"exitApplication" : "애플리케이션 종료", +"error" : "오류", +"gadgetUpdateAvailable" : "애플리케이션 업데이트 사용 가능", +"getNewVersion" : "새 버전 가져오기", +"handleTimeOut" : "{0}에 대한 요청 제한시간이 초과되었습니다. 애플리케이션에 대한 호스트 주소가 사용 가능한지 확인하십시오(특히 Android 및 iPhone 애플리케이션 관련).", +"invalidUsernamePassword" : "사용자 이름 또는 비밀번호가 올바르지 않음", +"keepAliveInBackgroundText" : "애플리케이션이 배경에서 계속 실행됨", +"loading" : "로드 중", +"login" : "로그인", +"minimize" : "최소화", +"missingFeatureException" : "애플리케이션에서 {0}이(가) 누락되어 {1} 호출에 실패했습니다. 애플리케이션 디스크립터에 {0}을(를) 추가하고 다시 빌드하여 배치하십시오. ", +"name" : "이름:", +"noInternet" : "서비스에 대한 연결을 사용할 수 없습니다.", +"notificationTitle" : "서비스 알림", +"notAvailable" : "사용할 수 없음", +"ok" : "확인", +"password" : "비밀번호:", +"reload" : "다시 로드", +"restore" : "복원", +"requestTimeout" : "애플리케이션에서 서비스 연결에 실패했습니다.", +"responseNotRecognized" : "예상치 못한 응답입니다.", +"settings" : "설정", +"serverError" : "프로시저 호출 오류입니다.", +"tryAgain" : "다시 시도", +"userInstanceAccessViolationException" : "등록되지 않은 애플리케이션에 로그인하는 중입니다.", +"unexpectedError" : "서버는 애플리케이션의 요청을 처리할 수 없습니다. 나중에 다시 시도하십시오.", +"unresponsiveHost" : "서비스를 현재 사용할 수 없습니다.", +"update" : "업데이트", +"upgrade" : "업그레이드", +"upgradeGadget" : "사용자의 애플리케이션 버전은 {0}입니다. 이 애플리케이션의 {1} 버전을 사용할 수 있습니다. 해당 버전을 다운로드하여 설치하려면 확인을 클릭하십시오.", +"wlclientInitFailure" : "오류", +"wlSettings" : "IBM MobileFirst Platform 설정", +"userEnrollmentUnsupportedOS" : "사용자 인증 실패: 지원되지 않는 클라이언트 플랫폼", +"failureCallingMethod" : "{0} 호출 실패", +"challengeHandlingCanceled" : "인증 확인 핸들러 조작이 취소되었습니다.", +"unsupportedEnvironment" : "지원되지 않는 환경", +"redirect" : "경로 재지정" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/messages.json new file mode 100644 index 000000000..7a295454d --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/messages.json @@ -0,0 +1,84 @@ +{ +"IBM-INTERNAL" : "PII user facing" , +"accessDenied" : "Access Denied", +"authFailure" : "An error was encountered while processing the request from the application.", +"applicationDenied" : "Application Disabled", +"browserIsNotSupported" : "{0} is currently not supported.", +"cancel" : "Cancel", +"close" : "Close", +"cookiesAreDisabled" : "Cookies are currently disabled in your browser. You must enable them for the application to function properly.", +"copyToClipboard" : "Copy", +"details" : "Details", +"diagApp" : "App Diagnostics", +"diagTime" : "Time", +"diagApplicationName" : "Application Name", +"diagApplicationVersion" : "Application Version", +"diagServiceURL" : "Service URL", +"diagDevicePlatform" : "Device Platform", +"diagDeviceVersion" : "Device Version", +"diagScreenResolution" : "Screen Resolution", +"diagAirplaneMode" : "Airplane Mode", +"diagUsingNetwork" : "Using Network", +"diagWifiName" : "WiFi Name", +"diagMobileNetworkType" : "Mobile Network Type", +"diagCarrierName" : "Carrier Name", +"diagErrorCode" : "Error Code", +"diagErrorMessage" : "Error Message", +"diagHttpStatus" : "HTTP Status", +"diagIPAddress" : "IP Address", +"directUpdateNotificationTitle" : "Update available", +"directUpdateNotificationMessage" : "Newer web resources are available. Confirm to update. (file size is {0} MB).", +"directUpdateNotificationMessageKilobytes" : "Newer web resources are available. Confirm to update. (file size is {0} KB).", +"directUpdateErrorTitle" : "Update Failed", +"directUpdateErrorMessage" : "Direct Update failure.", +"directUpdateErrorMessageNotEnoughStorage" : "An update for the application is available, but there is not enough space available on the device (required size: {0} MB, available space: {1} MB).", +"directUpdateErrorMessageFailedDownloadingZipFile" : "Failed downloading application update file.", +"directUpdateErrorMessageFailedProcessingZipFile" : "Failed processing application update file.", +"downloadAppWebResourcesPleaseSpecifyAppID" : "Cannot download app resources. Specify the App ID in the Settings screen.", +"downloadAppWebResourcesAppIdNotExist" : "Application '{0}' cannot be found. Deploy it first to the IBM MobileFirst Platform Server.", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "Cannot download app resources. Specify the App Version in the Settings screen.", +"downloadAppWebResourcesSkinIsNotValid" : "Cannot download app resources. skin: {0} does not exist. Please make sure getSkinName() resolves to a valid skin.", +"downloadAppWebResourcesAppVersionNotExist" : "Cannot find application '{0}' {1} for {2}", +"deviceAuthenticationFail" : "Connectivity Error", +"saveCertificateFailure" : "Unable to save certificate", +"downloadAppWebResourcesConnectionToServerUnavailable" : "A connection to the server is not available. Cannot download application resources.", +"expandWindow" : "Expand application to use it", +"exit" : "Exit", +"exitApplication" : "Exit application", +"error" : "Error", +"gadgetUpdateAvailable" : "Application update available", +"getNewVersion" : "Get new version", +"handleTimeOut" : "Request timed out for {0}. Make sure the host address is available to the application (especially relevant for Android and iPhone apps).", +"invalidUsernamePassword" : "Invalid user name or password", +"keepAliveInBackgroundText" : "Application keeps running in background", +"loading" : "Loading", +"login" : "Login", +"minimize" : "Minimize", +"missingFeatureException" : "Failed to call {1} because {0} is missing in the application. Add {0} to the application descriptor, rebuild and deploy it.", +"name" : "Name:", +"noInternet" : "Connection to the service is not available.", +"notificationTitle" : "Service Notification", +"notAvailable" : "Not Available", +"ok" : "OK", +"password" : "Password:", +"reload" : "Reload", +"restore" : "Restore", +"requestTimeout" : "The application failed connecting to the service.", +"responseNotRecognized" : "Unexpected response.", +"settings" : "Settings", +"serverError" : "Procedure invocation error.", +"tryAgain" : "Try Again", +"userInstanceAccessViolationException" : "You are trying to login to an application that is not registered for you.", +"unexpectedError" : "The server was unable to process the request from the application. Please try again later.", +"unresponsiveHost" : "The service is currently not available.", +"update" : "Update", +"upgrade" : "Upgrade", +"upgradeGadget" : "The version of your application is {0}. Version {1} of this application is available. Click OK to download and install it.", +"wlclientInitFailure" : "Error", +"wlSettings" : "IBM MobileFirst Platform Settings", +"userEnrollmentUnsupportedOS" : "User certificate authentication failure: unsupported client platform.", +"failureCallingMethod" : "Failure calling {0}", +"challengeHandlingCanceled" : "Challenge handler operation was cancelled.", +"unsupportedEnvironment" : "Unsupported environment", +"redirect" : "Redirect" +} diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/pt-BR/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/pt-BR/messages.json new file mode 100644 index 000000000..bde18698b --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/pt-BR/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "Face de Usuário de PII" , +"accessDenied" : "Acesso Negado", +"authFailure" : "Um erro foi encontrado ao processar a solicitação do aplicativo.", +"applicationDenied" : "Aplicativo Desativado", +"browserIsNotSupported" : "{0} não é suportado atualmente.", +"cancel" : "Cancelar", +"close" : "Fechar", +"cookiesAreDisabled" : "Os cookies estão atualmente desativados em seu navegador. Você deve ativá-los para que o aplicativo funcione adequadamente.", +"copyToClipboard" : "Copiar", +"details" : "Detalhes", +"diagApp" : "Diagnósticos do Aplicativo", +"diagTime" : "Tempo", +"diagApplicationName" : "Nome do Aplicativo", +"diagApplicationVersion" : "Versão do Aplicativo", +"diagServiceURL" : "URL de Serviço", +"diagDevicePlatform" : "Plataforma do Dispositivo", +"diagDeviceVersion" : "Versão do Dispositivo", +"diagScreenResolution" : "Resolução da Tela", +"diagAirplaneMode" : "Modo de Avião", +"diagUsingNetwork" : "Usando Rede", +"diagWifiName" : "Nome da WiFi", +"diagMobileNetworkType" : "Tipo de Rede Remota", +"diagCarrierName" : "Nome da Transportadora", +"diagErrorCode" : "Código de Erro", +"diagErrorMessage" : "Mensagem de Erro", +"diagHttpStatus" : "Status de HTTP", +"diagIPAddress" : "Endereço IP", +"directUpdateNotificationTitle" : "Atualização disponível", +"directUpdateNotificationMessage" : "Recursos da web mais recentes estão disponíveis. Confirme para atualizar. (o tamanho do arquivo é de {0} MB).", +"directUpdateNotificationMessageKilobytes" : "Recursos da web mais recentes estão disponíveis. Confirme para atualizar. (o tamanho do arquivo é de {0} KB).", +"directUpdateErrorTitle" : "Atualização com Falha", +"directUpdateErrorMessage" : "Falha na Atualização Direta.", +"directUpdateErrorMessageNotEnoughStorage" : "Uma atualização para o aplicativo está disponível, mas não há espaço suficiente disponível no dispositivo (tamanho necessário: {0} MB, espaço disponível: {1} MB).", +"directUpdateErrorMessageFailedDownloadingZipFile" : "Falha ao fazer download do arquivo de atualização do aplicativo.", +"directUpdateErrorMessageFailedProcessingZipFile" : "Falha ao processar o arquivo de atualização do aplicativo.", +"downloadAppWebResourcesPleaseSpecifyAppID" : "Não é possível fazer download de recursos do aplicativo. Especifique o ID do Aplicativo na tela Configurações.", +"downloadAppWebResourcesAppIdNotExist" : "O aplicativo '{0}' não pode ser localizado. Implemente-o primeiro no IBM MobileFirst Platform Server.", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "Não é possível fazer download de recursos do aplicativo. Especifique a Versão do Aplicativo na tela Configurações.", +"downloadAppWebResourcesSkinIsNotValid" : "Não é possível fazer download de recursos do aplicativo. Aparência: {0} não existe. Certifique-se de que getSkinName() seja resolvido para uma aparência válida.", +"downloadAppWebResourcesAppVersionNotExist" : "Não é possível localizar o aplicativo '{0}' {1} para {2}", +"deviceAuthenticationFail" : "Erro de Conectividade", +"saveCertificateFailure" : "Não é possível salvar o certificado", +"downloadAppWebResourcesConnectionToServerUnavailable" : "Uma conexão com o servidor não está disponível. Não é possível fazer download de recursos do aplicativo.", +"expandWindow" : "Expandir aplicativo para usá-lo", +"exit" : "Sair", +"exitApplication" : "Sair do Aplicativo", +"error" : "Erro", +"gadgetUpdateAvailable" : "Atualização do aplicativo disponível", +"getNewVersion" : "Obter Nova Versão", +"handleTimeOut" : "A solicitação atingiu o tempo limite para {0}. Certifique-se de que o endereço do host esteja disponível para o aplicativo (especialmente relevante para aplicativos Android e iPhone).", +"invalidUsernamePassword" : "Nome de usuário ou senha inválida", +"keepAliveInBackgroundText" : "O aplicativo continua em execução no segundo plano", +"loading" : "Carregando", +"login" : "Login", +"minimize" : "Minimizar", +"missingFeatureException" : "Falha ao chamar {1} porque {0} está ausente no aplicativo. Inclua {0} no descritor de aplicativo, reconstrua e implemente-o.", +"name" : "Nome:", +"noInternet" : "A conexão com o serviço não está disponível.", +"notificationTitle" : "Notificação de Serviço", +"notAvailable" : "Não Disponível", +"ok" : "OK", +"password" : "Senha:", +"reload" : "Recarregar", +"restore" : "Restaurar", +"requestTimeout" : "Falha ao conectar o aplicativo com o serviço.", +"responseNotRecognized" : "Resposta inesperada.", +"settings" : "Configurações", +"serverError" : "Erro de chamada de procedimento.", +"tryAgain" : "Tentar Novamente", +"userInstanceAccessViolationException" : "Você está tentando efetuar login em um aplicativo que não está registrado para você.", +"unexpectedError" : "O servidor não pôde processar a solicitação do aplicativo. Tente novamente mais tarde.", +"unresponsiveHost" : "O serviço não está atualmente disponível.", +"update" : "Atualizar", +"upgrade" : "Fazer Upgrade", +"upgradeGadget" : "A versão de seu aplicativo é {0}. A versão {1} deste aplicativo está disponível. Clique em OK para fazer download e instalá-la.", +"wlclientInitFailure" : "Erro", +"wlSettings" : "Configurações do IBM MobileFirst Platform", +"userEnrollmentUnsupportedOS" : "Falha de autenticação de certificado de usuário: plataforma do cliente não suportada.", +"failureCallingMethod" : "Falha ao chamar {0}", +"challengeHandlingCanceled" : "A operação do manipulador de desafios foi cancelada.", +"unsupportedEnvironment" : "Ambiente não suportado", +"redirect" : "Redirecionar" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/ru/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/ru/messages.json new file mode 100644 index 000000000..3495a5906 --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/ru/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "Личные данные для пользователя" , +"accessDenied" : "Доступ запрещен", +"authFailure" : "Произошла ошибка при обработке запроса из приложения.", +"applicationDenied" : "Приложение выключено", +"browserIsNotSupported" : "{0} в настоящее время не поддерживается.", +"cancel" : "Отмена", +"close" : "Закрыть", +"cookiesAreDisabled" : "В данный момент cookie в браузере выключены. Их необходимо включить, чтобы обеспечить правильную работу приложения.", +"copyToClipboard" : "Копировать", +"details" : "Сведения", +"diagApp" : "Диагностика приложения", +"diagTime" : "Время", +"diagApplicationName" : "Имя приложения", +"diagApplicationVersion" : "Версия приложения", +"diagServiceURL" : "URL службы", +"diagDevicePlatform" : "Платформа", +"diagDeviceVersion" : "Версия устройства", +"diagScreenResolution" : "Разрешение экрана", +"diagAirplaneMode" : "Режим самолета", +"diagUsingNetwork" : "Работа с сетью", +"diagWifiName" : "Имя WiFi", +"diagMobileNetworkType" : "Тип моб. сети", +"diagCarrierName" : "Назв. носителя", +"diagErrorCode" : "Код ошибки", +"diagErrorMessage" : "Сообщение", +"diagHttpStatus" : "Состояние HTTP", +"diagIPAddress" : "IP-адрес", +"directUpdateNotificationTitle" : "Есть обновление", +"directUpdateNotificationMessage" : "Доступные новые веб-ресурсы. Подтвердите обновление. (Размер файла: {0} МБ).", +"directUpdateNotificationMessageKilobytes" : "Доступные новые веб-ресурсы. Подтвердите обновление. (Размер файла: {0} КБ).", +"directUpdateErrorTitle" : "Обновление не выполнено", +"directUpdateErrorMessage" : "Сбой прямого обновления.", +"directUpdateErrorMessageNotEnoughStorage" : "Для приложения доступно обновление, но в устройстве недостаточно памяти (необходимый объем: {0} МБ, доступно: {1} МБ).", +"directUpdateErrorMessageFailedDownloadingZipFile" : "Не удалось загрузить файл обновления приложения.", +"directUpdateErrorMessageFailedProcessingZipFile" : "Не удалось обработать файл обновления приложения.", +"downloadAppWebResourcesPleaseSpecifyAppID" : "Невозможно загрузить ресурсы приложения. Укажите ИД приложения в окне Параметры.", +"downloadAppWebResourcesAppIdNotExist" : "Приложение '{0}' не найдено. Разверните его сначала на сервере IBM MobileFirst Platform.", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "Невозможно загрузить ресурсы приложения. Укажите версию приложения в окне Параметры.", +"downloadAppWebResourcesSkinIsNotValid" : "Невозможно загрузить ресурсы приложения. оболочка {0} не существует. Убедитесь, что getSkinName() преобразуется в правильную оболочку.", +"downloadAppWebResourcesAppVersionNotExist" : "Не найдено приложение '{0}' {1} для {2}", +"deviceAuthenticationFail" : "Ошибка соединения", +"saveCertificateFailure" : "Не удалось сохранить сертификат", +"downloadAppWebResourcesConnectionToServerUnavailable" : "Соединение с сервером недоступно. Невозможно загрузить ресурсы приложения.", +"expandWindow" : "Разверните приложение для работы с ним", +"exit" : "Выход", +"exitApplication" : "Закрыть приложение", +"error" : "Ошибка", +"gadgetUpdateAvailable" : "Доступно обновление приложения", +"getNewVersion" : "Получить новую версию", +"handleTimeOut" : "Тайм-аут запроса для {0}. Убедитесь, что адрес хоста доступен для приложения (особенно при работе с приложениями Android и iPhone).", +"invalidUsernamePassword" : "Недопустимое имя пользователя или пароль", +"keepAliveInBackgroundText" : "Приложение продолжает выполняться в фоновом режиме", +"loading" : "Загрузка", +"login" : "Вход в систему", +"minimize" : "Свернуть", +"missingFeatureException" : "Не удалось вызвать {1}, так как в приложении отсутствует {0}. Добавьте {0} в файл описания приложения, перекомпонуйте его и выполните развертывание.", +"name" : "Имя:", +"noInternet" : "Соединение со службой недоступно.", +"notificationTitle" : "Уведомление об обслуживании", +"notAvailable" : "Недоступно", +"ok" : "OK", +"password" : "Пароль:", +"reload" : "Заново", +"restore" : "Восстановить", +"requestTimeout" : "Приложению не удалось подключиться к службе.", +"responseNotRecognized" : "Непредвиденный ответ.", +"settings" : "Параметры", +"serverError" : "Ошибка вызова процедуры.", +"tryAgain" : "Повторите попытку", +"userInstanceAccessViolationException" : "Попытка входа в систему приложения, которое для вас не зарегистрировано.", +"unexpectedError" : "Серверу не удалось обработать запрос из приложения. Повторите операцию позднее.", +"unresponsiveHost" : "В данный момент служба недоступна.", +"update" : "Обновить", +"upgrade" : "Обновить", +"upgradeGadget" : "Приложение имеет версию {0}. Доступна версия {1} этого приложения. Для загрузки и установки нажмите OK.", +"wlclientInitFailure" : "Ошибка", +"wlSettings" : "Параметры платформы IBM MobileFirst", +"userEnrollmentUnsupportedOS" : "Ошибка сертификата пользователя: неподдерживаемая платформа клиента.", +"failureCallingMethod" : "Ошибка вызова {0}", +"challengeHandlingCanceled" : "Операция обработчика вызовов была отменена.", +"unsupportedEnvironment" : "Неподдерживаемая среда", +"redirect" : "Перенаправление" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/zh-TW/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/zh-TW/messages.json new file mode 100644 index 000000000..2785177d0 --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/zh-TW/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "PII 使用者樣式" , +"accessDenied" : "拒絕存取", +"authFailure" : "處理應用程式的要求時發生錯誤。", +"applicationDenied" : "應用程式已停用", +"browserIsNotSupported" : "目前不支援 {0}。", +"cancel" : "取消", +"close" : "關閉", +"cookiesAreDisabled" : "您的瀏覽器目前已停用 Cookie。 您必須加以啟用,應用程式才能正常運作。", +"copyToClipboard" : "複製", +"details" : "詳細資料", +"diagApp" : "應用程式診斷程式", +"diagTime" : "時間", +"diagApplicationName" : "應用程式名稱", +"diagApplicationVersion" : "應用程式版本", +"diagServiceURL" : "服務 URL", +"diagDevicePlatform" : "裝置平台", +"diagDeviceVersion" : "裝置版本", +"diagScreenResolution" : "螢幕解析度", +"diagAirplaneMode" : "飛航模式", +"diagUsingNetwork" : "使用網路", +"diagWifiName" : "WiFi 名稱", +"diagMobileNetworkType" : "行動網路類型", +"diagCarrierName" : "電信業者名稱", +"diagErrorCode" : "錯誤碼", +"diagErrorMessage" : "錯誤訊息", +"diagHttpStatus" : "HTTP 狀態", +"diagIPAddress" : "IP 位址", +"directUpdateNotificationTitle" : "有可用的更新項目", +"directUpdateNotificationMessage" : "有可用的新版 Web 資源。確認以更新。(檔案大小為 {0} MB)。", +"directUpdateNotificationMessageKilobytes" : "有可用的新版 Web 資源。確認以更新。(檔案大小為 {0} KB)。", +"directUpdateErrorTitle" : "更新失敗", +"directUpdateErrorMessage" : "直接更新失敗。", +"directUpdateErrorMessageNotEnoughStorage" : "有可用的應用程式更新,但裝置上的可用空間不足(需要大小:{0} MB,可用空間:{1} MB)。", +"directUpdateErrorMessageFailedDownloadingZipFile" : "下載應用程式更新檔案失敗。", +"directUpdateErrorMessageFailedProcessingZipFile" : "處理應用程式更新檔案失敗。", +"downloadAppWebResourcesPleaseSpecifyAppID" : "無法下載應用程式資源。 請在「設定」畫面中指定「應用程式 ID」。", +"downloadAppWebResourcesAppIdNotExist" : "找不到應用程式 '{0}'。 請先將其部署到 IBM MobileFirst Platform Server。", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "無法下載應用程式資源。 請在「設定」畫面中指定「應用程式版本」。", +"downloadAppWebResourcesSkinIsNotValid" : "無法下載應用程式資源。 外觀:{0} 不存在。 請確定 getSkinName() 可解析為有效的外觀。", +"downloadAppWebResourcesAppVersionNotExist" : "找不到 {2} 適用的應用程式 '{0}' {1}", +"deviceAuthenticationFail" : "連線功能錯誤", +"saveCertificateFailure" : "無法儲存憑證", +"downloadAppWebResourcesConnectionToServerUnavailable" : "目前無法連線至伺服器。 無法下載應用程式資源。", +"expandWindow" : "展開應用程式加以使用", +"exit" : "結束", +"exitApplication" : "結束應用程式", +"error" : "錯誤", +"gadgetUpdateAvailable" : "有可用的應用程式更新", +"getNewVersion" : "取得新版本", +"handleTimeOut" : "{0} 的要求逾時。 請確定應用程式(特別是 Android 與 iPhone 應用程式)可使用該主機位址。", +"invalidUsernamePassword" : "無效的使用者名稱或密碼", +"keepAliveInBackgroundText" : "應用程式繼續在背景中執行", +"loading" : "載入中", +"login" : "登入", +"minimize" : "最小化", +"missingFeatureException" : "應用程式中缺少 {0},所以無法呼叫 {1}。 請在應用程式描述子中新增 {0},然後再行重建及部署。", +"name" : "名稱:", +"noInternet" : "無法連線至服務。", +"notificationTitle" : "服務通知", +"notAvailable" : "無法使用", +"ok" : "確定", +"password" : "密碼:", +"reload" : "重新載入", +"restore" : "還原", +"requestTimeout" : "應用程式無法連線至服務。", +"responseNotRecognized" : "非預期的回應。", +"settings" : "設定", +"serverError" : "程序呼叫錯誤。", +"tryAgain" : "重試", +"userInstanceAccessViolationException" : "嘗試登入不是為您登錄的應用程式。", +"unexpectedError" : "伺服器無法處理應用程式的要求。請稍後再試一次。", +"unresponsiveHost" : "服務目前無法使用。", +"update" : "更新", +"upgrade" : "升級", +"upgradeGadget" : "您的應用程式版本為 {0}。此應用程式已有版本 {1} 可供使用。請按一下「確定」予以下載及安裝。", +"wlclientInitFailure" : "錯誤", +"wlSettings" : "IBM MobileFirst Platform 設定", +"userEnrollmentUnsupportedOS" : "使用者憑證鑑別失敗:不受支援的用戶端平台。", +"failureCallingMethod" : "呼叫 {0} 失敗", +"challengeHandlingCanceled" : "已取消盤查處理程式作業。", +"unsupportedEnvironment" : "不支援的環境", +"redirect" : "重新導向" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/zh/messages.json b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/zh/messages.json new file mode 100644 index 000000000..382b23823 --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/messages/zh/messages.json @@ -0,0 +1,85 @@ +{ +"IBM-INTERNAL" : "PII user facing" , +"accessDenied" : "拒绝访问", +"authFailure" : "处理应用程序请求时遇到错误。", +"applicationDenied" : "已禁用应用程序", +"browserIsNotSupported" : "当前不支持 {0}。", +"cancel" : "取消", +"close" : "关闭", +"cookiesAreDisabled" : "浏览器当前禁用 Cookie。 要使应用程序正常运行,必须启用 Cookie。", +"copyToClipboard" : "复制", +"details" : "详细信息", +"diagApp" : "应用程序诊断", +"diagTime" : "时间", +"diagApplicationName" : "应用程序名称", +"diagApplicationVersion" : "应用程序版本", +"diagServiceURL" : "服务 URL", +"diagDevicePlatform" : "设备平台", +"diagDeviceVersion" : "设备版本", +"diagScreenResolution" : "屏幕分辨率", +"diagAirplaneMode" : "飞行模式", +"diagUsingNetwork" : "使用网络", +"diagWifiName" : "WiFi 名称", +"diagMobileNetworkType" : "移动网络类型", +"diagCarrierName" : "运营商名称", +"diagErrorCode" : "错误代码", +"diagErrorMessage" : "错误消息", +"diagHttpStatus" : "HTTP 状态", +"diagIPAddress" : "IP 地址", +"directUpdateNotificationTitle" : "可用更新", +"directUpdateNotificationMessage" : "有新的 Web 资源可用,确认更新。(文件大小为 {0} MB)。", +"directUpdateNotificationMessageKilobytes" : "有新的 Web 资源可用,确认更新。(文件大小为 {0} KB)。", +"directUpdateErrorTitle" : "更新失败", +"directUpdateErrorMessage" : "直接更新失败。", +"directUpdateErrorMessageNotEnoughStorage" : "该应用程序有一个可用更新,但设备空间不足(所需大小:{0} MB,可用空间:{1} MB)。", +"directUpdateErrorMessageFailedDownloadingZipFile" : "下载应用程序更新文件失败。", +"directUpdateErrorMessageFailedProcessingZipFile" : "处理应用程序更新文件失败。", +"downloadAppWebResourcesPleaseSpecifyAppID" : "无法下载应用程序资源。 请在“设置”屏幕上指定应用程序标识。", +"downloadAppWebResourcesAppIdNotExist" : "找不到应用程序“{0}”。 请先将该应用程序部署到 IBM MobileFirst Platform 服务器。", +"downloadAppWebResourcesPleaseSpecifyAppVersion" : "无法下载应用程序资源。 请在“设置”屏幕上指定应用程序版本。", +"downloadAppWebResourcesSkinIsNotValid" : "无法下载应用程序资源。 外表:{0} 不存在。 请确保 getSkinName() 解析为有效外表。", +"downloadAppWebResourcesAppVersionNotExist" : "无法为 {2} 找到应用程序“{0}”{1}", +"deviceAuthenticationFail" : "连接错误", +"saveCertificateFailure" : "无法保存证书", +"downloadAppWebResourcesConnectionToServerUnavailable" : "无法连接至服务器。 无法下载应用程序资源。", +"expandWindow" : "扩展应用程序以使用它", +"exit" : "退出", +"exitApplication" : "退出应用程序", +"error" : "错误", +"gadgetUpdateAvailable" : "应用程序更新可用", +"getNewVersion" : "获取新版本", +"handleTimeOut" : "对 {0} 的请求超时。 请确保应用程序可以使用该主机地址(尤其适用于 Android 和 iPhone 应用程序)。", +"invalidUsernamePassword" : "无效的用户名或密码", +"keepAliveInBackgroundText" : "应用程序一直在后台运行", +"loading" : "正在装入", +"login" : "登录", +"minimize" : "最小化", +"missingFeatureException" : "由于应用程序缺少 {0},因此调用 {1} 失败。 请将 {0} 添加至应用程序描述符,然后将其重新构建并部署。", +"name" : "名称:", +"noInternet" : "服务连接不可用。", +"notificationTitle" : "服务通知", +"notAvailable" : "不可用", +"ok" : "确定", +"password" : "密码:", +"reload" : "重新装入", +"restore" : "复原", +"requestTimeout" : "应用程序无法连接至服务。", +"responseNotRecognized" : "意外响应。", +"settings" : "设置", +"serverError" : "过程调用错误。", +"tryAgain" : "请重试", +"userInstanceAccessViolationException" : "您正在尝试登录到自己尚未注册的应用程序。", +"unexpectedError" : "服务器无法处理该应用程序的请求。请稍后重试。", +"unresponsiveHost" : "服务当前不可用。", +"update" : "更新", +"upgrade" : "升级", +"upgradeGadget" : "您的应用程序版本为 {0}。该应用程序的 V{1} 可用。请单击“确定”以下载并安装该版本。", +"wlclientInitFailure" : "错误", +"wlSettings" : "IBM MobileFirst Platform 设置", +"userEnrollmentUnsupportedOS" : "用户证书认证失败:不受支持的客户机平台。", +"failureCallingMethod" : "调用 {0} 失败", +"challengeHandlingCanceled" : "验证问题处理程序操作已取消。", +"unsupportedEnvironment" : "不支持的环境", +"redirect" : "重定向" +} + diff --git a/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/wljq.js b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/wljq.js new file mode 100644 index 000000000..751509db4 --- /dev/null +++ b/_platforms/ios/platform_www/plugins/cordova-plugin-mfp/worklight/wljq.js @@ -0,0 +1,9753 @@ +/**! +* @license + Licensed Materials - Property of IBM + + (C) Copyright 2015 IBM Corp. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/*! + * jQuery JavaScript Library v1.9.1 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-2-4 + */ +(function( window, undefined ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +//"use strict"; +var + // The deferred used on DOM ready + readyList, + + // A central reference to the root jQuery(document) + rootjQuery, + + // Support: IE<9 + // For `typeof node.method` instead of `node.method !== undefined` + core_strundefined = typeof undefined, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + location = window.location, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // [[Class]] -> type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.9.1", + + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // The ready event handler + completed = function( event ) { + + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } + }, + // Clean-up method for dom ready events + detach = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call(obj) ] || "object" : + typeof obj; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); + }, + + parseJSON: function( data ) { + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + if ( data === null ) { + return data; + } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + core_push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function() { + + var support, all, a, + input, select, fragment, + opt, eventName, isSupported, i, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = "
a"; + }); + } + else { + div.innerHTML = "
a"; + } + + // Support tests won't run in some limited or non-browser environments + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + if ( !all || !a || !all.length ) { + return {}; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + support = { + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.5/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + checkOn: !!input.value, + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Tests for enctype support on a form (#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: document.compatMode === "CSS1Compat", + + // Will be defined later + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + // Anton::Patch to support WindowsPhone8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + body.appendChild( container ).appendChild( div ); + }); + } else { + body.appendChild( container ).appendChild( div ); + } + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = "
t
"; + }); + } + else { + div.innerHTML = "
t
"; + } + + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = ""; + }); + } + else { + div.innerHTML = ""; + } + + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement("div") ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== core_strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined"){ + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = ""; + }); + } + else { + div.innerHTML = ""; + } + + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined"){ + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = "
"; + }); + } + else { + div.innerHTML = "
"; + } + + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + if ( support.inlineBlockNeedsLayout ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; + + return support; +})(); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var i, l, thisCache, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var attrs, name, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; + + if ( !name.indexOf( "data-" ) ) { + name = jQuery.camelCase( name.slice(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + // Try to fetch any internally stored data first + return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + } + + this.each(function() { + jQuery.data( this, key, value ); + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + hooks.cur = fn; + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + }); + } + + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + }); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.match( core_rnotwhite ) || []; + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var ret, hooks, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val, + self = jQuery(this); + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var hooks, notxml, ret, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === core_strundefined ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + // In IE9+, Flash objects don't have .getAttribute (#12945) + // Support: IE9+ + if ( typeof elem.getAttribute !== core_strundefined ) { + ret = elem.getAttribute( name ); + } + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( rboolean.test( name ) ) { + // Set corresponding property to false for boolean attributes + // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8 + if ( !getSetAttribute && ruseDefault.test( name ) ) { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } else { + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + var + // Use .prop to determine if this attribute is understood as boolean + prop = jQuery.prop( elem, name ), + + // Fetch it accordingly + attr = typeof prop === "boolean" && elem.getAttribute( name ), + detail = typeof prop === "boolean" ? + + getSetInput && getSetAttribute ? + attr != null : + // oldIE fabricates an empty string for missing boolean attributes + // and conflates checked/selected into attroperties + ruseDefault.test( name ) ? + elem[ jQuery.camelCase( "default-" + name ) ] : + !!attr : + + // fetch an attribute node for properties not recognized as boolean + elem.getAttributeNode( name ); + + return detail && detail.value !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; + +// fix oldIE value attroperty +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return jQuery.nodeName( elem, "input" ) ? + + // Ignore the value *property* by using defaultValue + elem.defaultValue : + + ret && ret.specified ? ret.value : undefined; + }, + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ? + ret.value : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret == null ? undefined : ret; + } + }); + }); + + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + event.isTrigger = true; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur != this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + } + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== document.activeElement && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === document.activeElement && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === core_strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); +/*! + * Sizzle CSS Selector Engine + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ +(function( window, undefined ) { + +var i, + cachedruns, + Expr, + getText, + isXML, + compile, + hasDuplicate, + outermostContext, + + // Local document vars + setDocument, + document, + docElem, + documentIsXML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + sortOrder, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + support = {}, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Array methods + arr = [], + pop = arr.pop, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = "([*^$|!~]?=)", + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rsibling = /[\x20\t\r\n\f]*[+~]/, + + rnative = /^[^{]+\{\s*\[native code/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, + funescape = function( _, escaped ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + return high !== high ? + escaped : + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Use a stripped-down slice if we can't use a native one +try { + slice.call( preferredDoc.documentElement.childNodes, 0 )[0].nodeType; +} catch ( e ) { + slice = function( i ) { + var elem, + results = []; + while ( (elem = this[i++]) ) { + results.push( elem ); + } + return results; + }; +} + +/** + * For feature detection + * @param {Function} fn The function to test for native support + */ +function isNative( fn ) { + return rnative.test( fn + "" ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var cache, + keys = []; + + return (cache = function( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key ] = value); + }); +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return fn( div ); + } catch (e) { + return false; + } finally { + // release memory in IE + div = null; + } +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( !documentIsXML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) { + push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); + return results; + } + } + + // QSA path + if ( support.qsa && !rbuggyQSA.test(selector) ) { + old = true; + nid = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, slice.call( newContext.querySelectorAll( + newSelector + ), 0 ) ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsXML = isXML( doc ); + + // Check if getElementsByTagName("*") returns only elements + support.tagNameNoComments = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if attributes should be retrieved by attribute nodes + support.attributes = assert(function( div ) { + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = ""; + }); + } + else { + div.innerHTML = ""; + } + + var type = typeof div.lastChild.getAttribute("multiple"); + // IE8 returns a string for some attributes even when not present + return type !== "boolean" && type !== "string"; + }); + + // Check if getElementsByClassName can be trusted + support.getByClassName = assert(function( div ) { + // Opera can't find a second classname (in 9.6) + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = ""; + }); + } + else { + div.innerHTML = ""; + } + + if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { + return false; + } + + // Safari 3.2 caches class attributes and doesn't catch changes + div.lastChild.className = "e"; + return div.getElementsByClassName("e").length === 2; + }); + + // Check if getElementById returns elements by name + // Check if getElementsByName privileges form controls or returns elements by ID + support.getByName = assert(function( div ) { + // Inject content + div.id = expando + 0; + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined"){ + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = "
"; + }); + } + else { + div.innerHTML = "
"; + } + + docElem.insertBefore( div, docElem.firstChild ); + + // Test + var pass = doc.getElementsByName && + // buggy browsers will return fewer than the correct 2 + doc.getElementsByName( expando ).length === 2 + + // buggy browsers will return more than the correct 0 + doc.getElementsByName( expando + 0 ).length; + support.getIdNotName = !doc.getElementById( expando ); + + // Cleanup + docElem.removeChild( div ); + + return pass; + }); + + // IE6/7 return modified attributes + Expr.attrHandle = assert(function( div ) { + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = ""; + }); + } + else { + div.innerHTML = ""; + } + + return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute("href") === "#"; + }) ? + {} : + { + "href": function( elem ) { + return elem.getAttribute( "href", 2 ); + }, + "type": function( elem ) { + return elem.getAttribute("type"); + } + }; + + // ID find and filter + if ( support.getIdNotName ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + + return m ? + m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? + [m] : + undefined : + []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.tagNameNoComments ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Name + Expr.find["NAME"] = support.getByName && function( tag, context ) { + if ( typeof context.getElementsByName !== strundefined ) { + return context.getElementsByName( name ); + } + }; + + // Class + Expr.find["CLASS"] = support.getByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) { + return context.getElementsByClassName( className ); + } + }; + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21), + // no need to also add to buggyMatches since matches checks buggyQSA + // A support test would require too much code (would include document ready) + rbuggyQSA = [ ":focus" ]; + + if ( (support.qsa = isNative(doc.querySelectorAll)) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explictly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = ""; + }); + } + else { + div.innerHTML = ""; + } + + + // IE8 - Some boolean attributes are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Opera 10-12/IE8 - ^= $= *= and empty values + // Should not select anything + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + div.innerHTML = ""; + }); + } + else { + div.innerHTML = ""; + } + + if ( div.querySelectorAll("[i^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = new RegExp( rbuggyMatches.join("|") ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + var compare; + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) { + if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { + if ( a === doc || contains( preferredDoc, a ) ) { + return -1; + } + if ( b === doc || contains( preferredDoc, b ) ) { + return 1; + } + return 0; + } + return compare & 4 ? -1 : 1; + } + + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + // Always assume the presence of duplicates if sort doesn't + // pass them to our comparison function (as in Google Chrome). + hasDuplicate = false; + [0, 0].sort( sortOrder ); + support.detectDuplicates = hasDuplicate; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + // rbuggyQSA always contains :focus, so no need for an existence check + if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + var val; + + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( !documentIsXML ) { + name = name.toLowerCase(); + } + if ( (val = Expr.attrHandle[ name ]) ) { + return val( elem ); + } + if ( documentIsXML || support.attributes ) { + return elem.getAttribute( name ); + } + return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? + name : + val && val.specified ? val.value : null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +// Document sorting and removing duplicates +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + i = 1, + j = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( ; (elem = results[i]); i++ ) { + if ( elem === results[ i - 1 ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +// Returns a function to use in pseudos for input types +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +// Returns a function to use in pseudos for buttons +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +// Returns a function to use in pseudos for positionals +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[4] ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeName ) { + if ( nodeName === "*" ) { + return function() { return true; }; + } + + nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifider + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsXML ? + elem.getAttribute("xml:lang") || elem.getAttribute("lang") : + elem.lang) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push( { + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && !documentIsXML && + Expr.relative[ tokens[1].type ] ) { + + context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0]; + if ( !context ) { + return results; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, slice.call( seed, 0 ) ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + documentIsXML, + results, + rsibling.test( selector ) + ); + return results; +} + +// Deprecated +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Easy API for creating new setFilters +function setFilters() {} +Expr.filters = setFilters.prototype = Expr.pseudos; +Expr.setFilters = new setFilters(); + +// Initialize with the default document +setDocument(); + +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, ret, self, + len = this.length; + + if ( typeof selector !== "string" ) { + self = this; + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + ret = []; + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, this[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = ( this.selector ? this.selector + " " : "" ) + selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true) ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + cur = this[i]; + + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + } + cur = cur.parentNode; + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +jQuery.fn.andSelf = jQuery.fn.addBack; + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( this.length > 1 && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !jQuery.support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + elem.innerHTML = value; + }); + } + else { + elem.innerHTML = value; + } + + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function( value ) { + var isFunc = jQuery.isFunction( value ); + + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( !isFunc && typeof value !== "string" ) { + value = jQuery( value ).not( this ).detach(); + } + + return this.domManip( [ value ], true, function( elem ) { + var next = this.nextSibling, + parent = this.parentNode; + + if ( parent ) { + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + }); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, table ? self.html() : undefined ); + } + self.domManip( args, table, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( + table && jQuery.nodeName( this[i], "table" ) ? + findOrAppend( this[i], "tbody" ) : + this[i], + node, + i + ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery.ajax({ + url: node.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + var attr = elem.getAttributeNode("type"); + elem.type = ( attr && attr.specified ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined"){ + MSApp.execUnsafeLocalFunction(function(){ + dest.innerHTML = src.innerHTML; + }); + } + else { + dest.innerHTML = src.innerHTML; + } + + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + fragmentDiv.innerHTML = elem.outerHTML; + }); + } + else { + fragmentDiv.innerHTML = elem.outerHTML; + } + + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + // Support for Windows 8 and Windows Phone 8 + if (typeof(MSApp) !== "undefined" && typeof(MSApp.execUnsafeLocalFunction) !== "undefined") { + MSApp.execUnsafeLocalFunction(function(){ + tmp.innerHTML = wrap[1] + elem + wrap[2]; + }); + } + else { + tmp.innerHTML = wrap[1] + elem + wrap[2]; + } + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + } +}); +var iframe, getStyles, curCSS, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + var bool = typeof state === "boolean"; + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var num, val, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("