monocle-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.DS_Store +0 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/lib/monocle/rails.rb +8 -0
  8. data/lib/monocle/rails/version.rb +5 -0
  9. data/monocle-rails.gemspec +23 -0
  10. data/vendor/.DS_Store +0 -0
  11. data/vendor/assets/.DS_Store +0 -0
  12. data/vendor/assets/javascripts/.DS_Store +0 -0
  13. data/vendor/assets/javascripts/compat/browser.js +120 -0
  14. data/vendor/assets/javascripts/compat/css.js +145 -0
  15. data/vendor/assets/javascripts/compat/env.js +463 -0
  16. data/vendor/assets/javascripts/compat/gala.js +469 -0
  17. data/vendor/assets/javascripts/compat/stubs.js +50 -0
  18. data/vendor/assets/javascripts/controls/contents.js +59 -0
  19. data/vendor/assets/javascripts/controls/magnifier.js +51 -0
  20. data/vendor/assets/javascripts/controls/panel.js +136 -0
  21. data/vendor/assets/javascripts/controls/placesaver.js +100 -0
  22. data/vendor/assets/javascripts/controls/scrubber.js +140 -0
  23. data/vendor/assets/javascripts/controls/spinner.js +99 -0
  24. data/vendor/assets/javascripts/controls/stencil.js +410 -0
  25. data/vendor/assets/javascripts/core/billboard.js +120 -0
  26. data/vendor/assets/javascripts/core/book.js +467 -0
  27. data/vendor/assets/javascripts/core/bookdata.js +59 -0
  28. data/vendor/assets/javascripts/core/component.js +413 -0
  29. data/vendor/assets/javascripts/core/events.js +56 -0
  30. data/vendor/assets/javascripts/core/factory.js +194 -0
  31. data/vendor/assets/javascripts/core/formatting.js +317 -0
  32. data/vendor/assets/javascripts/core/monocle.js +16 -0
  33. data/vendor/assets/javascripts/core/place.js +210 -0
  34. data/vendor/assets/javascripts/core/reader.js +683 -0
  35. data/vendor/assets/javascripts/core/selection.js +158 -0
  36. data/vendor/assets/javascripts/core/styles.js +155 -0
  37. data/vendor/assets/javascripts/dimensions/columns.js +218 -0
  38. data/vendor/assets/javascripts/flippers/instant.js +78 -0
  39. data/vendor/assets/javascripts/flippers/scroller.js +128 -0
  40. data/vendor/assets/javascripts/flippers/slider.js +469 -0
  41. data/vendor/assets/javascripts/monocore.js +27 -0
  42. data/vendor/assets/javascripts/monoctrl.js +1 -0
  43. data/vendor/assets/javascripts/panels/eink.js +61 -0
  44. data/vendor/assets/javascripts/panels/imode.js +180 -0
  45. data/vendor/assets/javascripts/panels/magic.js +297 -0
  46. data/vendor/assets/javascripts/panels/marginal.js +50 -0
  47. data/vendor/assets/javascripts/panels/twopane.js +34 -0
  48. data/vendor/assets/stylesheets/monocore.css +194 -0
  49. data/vendor/assets/stylesheets/monoctrl.css +168 -0
  50. metadata +129 -0
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in monocle-rails.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Monocle::Rails
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'monocle-rails'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install monocle-rails
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,8 @@
1
+ require "monocle/rails/version"
2
+
3
+ module Monocle
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Monocle
2
+ module Rails
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'monocle/rails/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "monocle-rails"
8
+ spec.version = Monocle::Rails::VERSION
9
+ spec.authors = ["Klaus Göttling"]
10
+ spec.email = ["klaus.goettling@hs-augsburg.de"]
11
+ spec.description = %q{A silky, tactile browser-based ebook reader. Initial development by Joseph Pearson of Inventive Labs. Released under the MIT license. More information (including demos): http://monocle.inventivelabs.com.au}
12
+ spec.summary = %q{Monocle from Joseph Pearson, bundled by Klaus Göttling}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
data/vendor/.DS_Store ADDED
Binary file
Binary file
Binary file
@@ -0,0 +1,120 @@
1
+ Monocle.Browser = {};
2
+
3
+ // Compare the user-agent string to a string or regex pattern.
4
+ //
5
+ Monocle.Browser.uaMatch = function (test) {
6
+ var ua = navigator.userAgent;
7
+ if (typeof test == "string") { return ua.indexOf(test) >= 0; }
8
+ return !!ua.match(test);
9
+ }
10
+
11
+
12
+ // Detect the browser engine and set boolean flags for reference.
13
+ //
14
+ Monocle.Browser.is = {
15
+ IE: !!(window.attachEvent && !Monocle.Browser.uaMatch('Opera')),
16
+ Opera: Monocle.Browser.uaMatch('Opera'),
17
+ WebKit: Monocle.Browser.uaMatch(/Apple\s?WebKit/),
18
+ Gecko: Monocle.Browser.uaMatch('Gecko') && !Monocle.Browser.uaMatch('KHTML'),
19
+ MobileSafari: Monocle.Browser.uaMatch(/OS \d_.*AppleWebKit.*Mobile/)
20
+ }
21
+
22
+
23
+ // Set the browser engine string.
24
+ //
25
+ if (Monocle.Browser.is.IE) {
26
+ Monocle.Browser.engine = "IE";
27
+ } else if (Monocle.Browser.is.Opera) {
28
+ Monocle.Browser.engine = "Opera";
29
+ } else if (Monocle.Browser.is.WebKit) {
30
+ Monocle.Browser.engine = "WebKit";
31
+ } else if (Monocle.Browser.is.Gecko) {
32
+ Monocle.Browser.engine = "Gecko";
33
+ } else {
34
+ console.warn("Unknown engine; assuming W3C compliant.");
35
+ Monocle.Browser.engine = "W3C";
36
+ }
37
+
38
+
39
+ // Detect the client platform (typically device/operating system).
40
+ //
41
+ Monocle.Browser.on = {
42
+ iPhone: Monocle.Browser.is.MobileSafari && screen.width == 320,
43
+ iPad: Monocle.Browser.is.MobileSafari && screen.width == 768,
44
+ UIWebView: (
45
+ Monocle.Browser.is.MobileSafari &&
46
+ !Monocle.Browser.uaMatch('Safari') &&
47
+ !navigator.standalone
48
+ ),
49
+ BlackBerry: Monocle.Browser.uaMatch('BlackBerry'),
50
+ Android: (
51
+ Monocle.Browser.uaMatch('Android') ||
52
+ Monocle.Browser.uaMatch('Silk') ||
53
+ Monocle.Browser.uaMatch(/Linux;.*EBRD/) // Sony Readers
54
+ ),
55
+ MacOSX: (
56
+ Monocle.Browser.uaMatch('Mac OS X') &&
57
+ !Monocle.Browser.is.MobileSafari
58
+ ),
59
+ Kindle3: Monocle.Browser.uaMatch(/Kindle\/3/)
60
+ }
61
+
62
+
63
+ // It is only because MobileSafari is responsible for so much anguish that
64
+ // we special-case it here. Not a badge of honour.
65
+ //
66
+ if (Monocle.Browser.is.MobileSafari) {
67
+ (function () {
68
+ var ver = navigator.userAgent.match(/ OS ([\d_]+)/);
69
+ if (ver) {
70
+ Monocle.Browser.iOSVersion = ver[1].replace(/_/g, '.');
71
+ } else {
72
+ console.warn("Unknown MobileSafari user agent: "+navigator.userAgent);
73
+ }
74
+ })();
75
+ }
76
+ Monocle.Browser.iOSVersionBelow = function (strOrNum) {
77
+ return !!Monocle.Browser.iOSVersion && Monocle.Browser.iOSVersion < strOrNum;
78
+ }
79
+
80
+
81
+ // Some browser environments are too slow or too problematic for
82
+ // special animation effects.
83
+ //
84
+ // FIXME: These tests are too opinionated. Replace with more targeted tests.
85
+ //
86
+ Monocle.Browser.renders = (function () {
87
+ var ua = navigator.userAgent;
88
+ var caps = {};
89
+ caps.eInk = Monocle.Browser.on.Kindle3;
90
+ caps.slow = (
91
+ caps.eInk ||
92
+ (Monocle.Browser.on.Android && !ua.match(/Chrome/)) ||
93
+ Monocle.Browser.on.Blackberry ||
94
+ ua.match(/NintendoBrowser/)
95
+ );
96
+ return caps;
97
+ })();
98
+
99
+
100
+ // A helper class for sniffing CSS features and creating CSS rules
101
+ // appropriate to the current rendering engine.
102
+ //
103
+ Monocle.Browser.css = new Monocle.CSS();
104
+
105
+
106
+ // During Reader initialization, this method is called to create the
107
+ // "environment", which tests for the existence of various browser
108
+ // features and bugs, then invokes the callback to continue initialization.
109
+ //
110
+ // If the survey has already been conducted and the env exists, calls
111
+ // callback immediately.
112
+ //
113
+ Monocle.Browser.survey = function (callback) {
114
+ if (!Monocle.Browser.env) {
115
+ Monocle.Browser.env = new Monocle.Env();
116
+ Monocle.Browser.env.survey(callback);
117
+ } else if (typeof callback == "function") {
118
+ callback();
119
+ }
120
+ }
@@ -0,0 +1,145 @@
1
+ // A class for manipulating CSS properties in a browser-engine-aware way.
2
+ //
3
+ Monocle.CSS = function () {
4
+
5
+ var API = { constructor: Monocle.CSS }
6
+ var k = API.constants = API.constructor;
7
+ var p = API.properties = {
8
+ guineapig: document.createElement('div')
9
+ }
10
+
11
+
12
+ // Returns engine-specific properties,
13
+ //
14
+ // eg:
15
+ //
16
+ // toCSSProps('transform')
17
+ //
18
+ // ... in WebKit, this will return:
19
+ //
20
+ // ['transform', '-webkit-transform']
21
+ //
22
+ function toCSSProps(prop) {
23
+ var props = [prop];
24
+ var eng = k.engines.indexOf(Monocle.Browser.engine);
25
+ if (eng) {
26
+ var pf = k.prefixes[eng];
27
+ if (pf) {
28
+ props.push(pf+prop);
29
+ }
30
+ }
31
+ return props;
32
+ }
33
+
34
+
35
+ // Returns an engine-specific CSS string.
36
+ //
37
+ // eg:
38
+ //
39
+ // toCSSDeclaration('column-width', '300px')
40
+ //
41
+ // ... in Mozilla, this will return:
42
+ //
43
+ // "column-width: 300px; -moz-column-width: 300px;"
44
+ //
45
+ function toCSSDeclaration(prop, val) {
46
+ var props = toCSSProps(prop);
47
+ for (var i = 0, ii = props.length; i < ii; ++i) {
48
+ props[i] += ": "+val+";";
49
+ }
50
+ return props.join("");
51
+ }
52
+
53
+
54
+ // Returns an array of DOM properties specific to this engine.
55
+ //
56
+ // eg:
57
+ //
58
+ // toDOMProps('column-width')
59
+ //
60
+ // ... in Opera, this will return:
61
+ //
62
+ // [columnWidth, OColumnWidth]
63
+ //
64
+ function toDOMProps(prop) {
65
+ var parts = prop.split('-');
66
+ for (var i = parts.length; i > 0; --i) {
67
+ parts[i] = capStr(parts[i]);
68
+ }
69
+
70
+ var props = [parts.join('')];
71
+ var eng = k.engines.indexOf(Monocle.Browser.engine);
72
+ if (eng) {
73
+ var pf = k.domprefixes[eng];
74
+ if (pf) {
75
+ parts[0] = capStr(parts[0]);
76
+ props.push(pf+parts.join(''));
77
+ }
78
+ }
79
+ return props;
80
+ }
81
+
82
+
83
+ // Is this exact property (or any in this array of properties) supported
84
+ // by this engine?
85
+ //
86
+ function supportsProperty(props) {
87
+ for (var i in props) {
88
+ if (p.guineapig.style[props[i]] !== undefined) { return true; }
89
+ }
90
+ return false;
91
+ } // Thanks modernizr!
92
+
93
+
94
+
95
+ // Is this property (or a prefixed variant) supported by this engine?
96
+ //
97
+ function supportsPropertyWithAnyPrefix(prop) {
98
+ return supportsProperty(toDOMProps(prop));
99
+ }
100
+
101
+
102
+ function supportsMediaQuery(query) {
103
+ var gpid = "monocle_guineapig";
104
+ p.guineapig.id = gpid;
105
+ var st = document.createElement('style');
106
+ st.textContent = query+'{#'+gpid+'{height:3px}}';
107
+ (document.head || document.getElementsByTagName('head')[0]).appendChild(st);
108
+ document.documentElement.appendChild(p.guineapig);
109
+
110
+ var result = p.guineapig.offsetHeight === 3;
111
+
112
+ st.parentNode.removeChild(st);
113
+ p.guineapig.parentNode.removeChild(p.guineapig);
114
+
115
+ return result;
116
+ } // Thanks modernizr!
117
+
118
+
119
+ function supportsMediaQueryProperty(prop) {
120
+ return supportsMediaQuery(
121
+ '@media (' + k.prefixes.join(prop+'),(') + 'monocle__)'
122
+ );
123
+ }
124
+
125
+
126
+ function capStr(wd) {
127
+ return wd ? wd.charAt(0).toUpperCase() + wd.substr(1) : "";
128
+ }
129
+
130
+
131
+ API.toCSSProps = toCSSProps;
132
+ API.toCSSDeclaration = toCSSDeclaration;
133
+ API.toDOMProps = toDOMProps;
134
+ API.supportsProperty = supportsProperty;
135
+ API.supportsPropertyWithAnyPrefix = supportsPropertyWithAnyPrefix;
136
+ API.supportsMediaQuery = supportsMediaQuery;
137
+ API.supportsMediaQueryProperty = supportsMediaQueryProperty;
138
+
139
+ return API;
140
+ }
141
+
142
+
143
+ Monocle.CSS.engines = ["W3C", "WebKit", "Gecko", "Opera", "IE", "Konqueror"];
144
+ Monocle.CSS.prefixes = ["", "-webkit-", "-moz-", "-o-", "-ms-", "-khtml-"];
145
+ Monocle.CSS.domprefixes = ["", "Webkit", "Moz", "O", "ms", "Khtml"];
@@ -0,0 +1,463 @@
1
+ // A class that tests the browser environment for required capabilities and
2
+ // known bugs (for which we have workarounds).
3
+ //
4
+ Monocle.Env = function () {
5
+
6
+ var API = { constructor: Monocle.Env }
7
+ var k = API.constants = API.constructor;
8
+ var p = API.properties = {
9
+ // Assign to a function before running survey in order to get
10
+ // results as they come in. The function should take two arguments:
11
+ // testName and value.
12
+ resultCallback: null
13
+ }
14
+
15
+ // These are private variables so they don't clutter up properties.
16
+ var css = Monocle.Browser.css;
17
+ var activeTestName = null;
18
+ var frameLoadCallback = null;
19
+ var testFrame = null;
20
+ var testFrameCntr = null;
21
+ var testFrameSize = 100;
22
+ var surveyCallback = null;
23
+
24
+
25
+ function survey(cb) {
26
+ surveyCallback = cb;
27
+ runNextTest();
28
+ }
29
+
30
+
31
+ function runNextTest() {
32
+ var test = envTests.shift();
33
+ if (!test) { return completed(); }
34
+ activeTestName = test[0];
35
+ try { test[1](); } catch (e) { result(e); }
36
+ }
37
+
38
+
39
+ // Each test should call this to say "I'm finished, run the next test."
40
+ //
41
+ function result(val) {
42
+ API[activeTestName] = val;
43
+ if (p.resultCallback) { p.resultCallback(activeTestName, val); }
44
+ runNextTest();
45
+ return val;
46
+ }
47
+
48
+
49
+ // Invoked after all tests have run.
50
+ //
51
+ function completed() {
52
+ // Remove the test frame after a slight delay (otherwise Gecko spins).
53
+ Monocle.defer(removeTestFrame);
54
+
55
+ if (typeof surveyCallback == "function") {
56
+ var fn = surveyCallback;
57
+ surveyCallback = null;
58
+ fn(API);
59
+ }
60
+ }
61
+
62
+
63
+ // A bit of sugar for simplifying a detection pattern: does this
64
+ // function exist?
65
+ //
66
+ // Pass a string snippet of JavaScript to be evaluated.
67
+ //
68
+ function testForFunction(str) {
69
+ return function () { result(typeof eval(str) == "function"); }
70
+ }
71
+
72
+
73
+ // A bit of sugar to indicate that the detection function for this test
74
+ // hasn't been written yet...
75
+ //
76
+ // Pass the value you want assigned for the test until it is implemented.
77
+ //
78
+ function testNotYetImplemented(rslt) {
79
+ return function () { result(rslt); }
80
+ }
81
+
82
+
83
+ // Loads (or reloads) a hidden iframe so that we can test browser features.
84
+ //
85
+ // cb is the callback that is fired when the test frame's content is loaded.
86
+ //
87
+ // src is optional, in which case it defaults to 4. If provided, it can be
88
+ // a number (specifying the number of pages of default content), or a string,
89
+ // which will be loaded as a URL.
90
+ //
91
+ function loadTestFrame(cb, src) {
92
+ if (!testFrame) { testFrame = createTestFrame(); }
93
+ frameLoadCallback = cb;
94
+
95
+ src = src || 4;
96
+
97
+ if (typeof src == "number") {
98
+ var pgs = [];
99
+ for (var i = 1, ii = src; i <= ii; ++i) {
100
+ pgs.push("<div>Page "+i+"</div>");
101
+ }
102
+ var divStyle = [
103
+ "display:inline-block",
104
+ "line-height:"+testFrameSize+"px",
105
+ "width:"+testFrameSize+"px"
106
+ ].join(";");
107
+ src = "javascript:'<!DOCTYPE html><html>"+
108
+ '<head><meta name="time" content="'+(new Date()).getTime()+'" />'+
109
+ '<style>div{'+divStyle+'}</style></head>'+
110
+ '<body>'+pgs.join("")+'</body>'+
111
+ "</html>'";
112
+ }
113
+
114
+ testFrame.src = src;
115
+ }
116
+
117
+
118
+ // Creates the hidden test frame and returns it.
119
+ //
120
+ function createTestFrame() {
121
+ testFrameCntr = document.createElement('div');
122
+ testFrameCntr.style.cssText = [
123
+ "width:"+testFrameSize+"px",
124
+ "height:"+testFrameSize+"px",
125
+ "overflow:hidden",
126
+ "position:absolute",
127
+ "visibility:hidden"
128
+ ].join(";");
129
+ document.body.appendChild(testFrameCntr);
130
+
131
+ var fr = document.createElement('iframe');
132
+ testFrameCntr.appendChild(fr);
133
+ fr.setAttribute("scrolling", "no");
134
+ fr.style.cssText = [
135
+ "width:100%",
136
+ "height:100%",
137
+ "border:none",
138
+ "background:#900"
139
+ ].join(";");
140
+ fr.addEventListener(
141
+ "load",
142
+ function () {
143
+ if (!fr.contentDocument || !fr.contentDocument.body) { return; }
144
+ var bd = fr.contentDocument.body;
145
+ bd.style.cssText = ([
146
+ "margin:0",
147
+ "padding:0",
148
+ "position:absolute",
149
+ "height:100%",
150
+ "width:100%",
151
+ "-webkit-column-width:"+testFrameSize+"px",
152
+ "-webkit-column-gap:0",
153
+ "-webkit-column-fill:auto",
154
+ "-moz-column-width:"+testFrameSize+"px",
155
+ "-moz-column-gap:0",
156
+ "-moz-column-fill:auto",
157
+ "-o-column-width:"+testFrameSize+"px",
158
+ "-o-column-gap:0",
159
+ "-o-column-fill:auto",
160
+ "column-width:"+testFrameSize+"px",
161
+ "column-gap:0",
162
+ "column-fill:auto"
163
+ ].join(";"));
164
+ if (bd.scrollHeight > testFrameSize) {
165
+ bd.style.cssText += ["min-width:200%", "overflow:hidden"].join(";");
166
+ if (bd.scrollHeight <= testFrameSize) {
167
+ bd.className = "column-force";
168
+ } else {
169
+ bd.className = "column-failed "+bd.scrollHeight;
170
+ }
171
+ }
172
+ frameLoadCallback(fr);
173
+ },
174
+ false
175
+ );
176
+ return fr;
177
+ }
178
+
179
+
180
+ function removeTestFrame() {
181
+ if (testFrameCntr && testFrameCntr.parentNode) {
182
+ testFrameCntr.parentNode.removeChild(testFrameCntr);
183
+ }
184
+ }
185
+
186
+
187
+ function columnedWidth(fr) {
188
+ var bd = fr.contentDocument.body;
189
+ var de = fr.contentDocument.documentElement;
190
+ return Math.max(bd.scrollWidth, de.scrollWidth);
191
+ }
192
+
193
+
194
+ var envTests = [
195
+
196
+ // TEST FOR REQUIRED CAPABILITIES
197
+
198
+ ["supportsW3CEvents", testForFunction("window.addEventListener")],
199
+ ["supportsCustomEvents", testForFunction("document.createEvent")],
200
+ ["supportsColumns", function () {
201
+ result(css.supportsPropertyWithAnyPrefix('column-width'));
202
+ }],
203
+ ["supportsTransform", function () {
204
+ result(css.supportsProperty([
205
+ 'transformProperty',
206
+ 'WebkitTransform',
207
+ 'MozTransform',
208
+ 'OTransform',
209
+ 'msTransform'
210
+ ]));
211
+ }],
212
+
213
+
214
+ // TEST FOR OPTIONAL CAPABILITIES
215
+
216
+ // Does it do CSS transitions?
217
+ ["supportsTransition", function () {
218
+ result(css.supportsPropertyWithAnyPrefix('transition'))
219
+ }],
220
+
221
+ // Can we find nodes in a document with an XPath?
222
+ //
223
+ ["supportsXPath", testForFunction("document.evaluate")],
224
+
225
+ // Can we find nodes in a document natively with a CSS selector?
226
+ //
227
+ ["supportsQuerySelector", testForFunction("document.querySelector")],
228
+
229
+ // Can we do 3d transforms?
230
+ //
231
+ ["supportsTransform3d", function () {
232
+ result(
233
+ css.supportsMediaQueryProperty('transform-3d') &&
234
+ css.supportsProperty([
235
+ 'perspectiveProperty',
236
+ 'WebkitPerspective',
237
+ 'MozPerspective',
238
+ 'OPerspective',
239
+ 'msPerspective'
240
+ ]) &&
241
+ !Monocle.Browser.renders.slow // Some older browsers can't be trusted.
242
+ );
243
+ }],
244
+
245
+
246
+ // Commonly-used browser functionality
247
+ ["supportsOfflineCache", function () {
248
+ result(typeof window.applicationCache != 'undefined');
249
+ }],
250
+
251
+ ["supportsLocalStorage", function () {
252
+ // NB: Some duplicitous early Android browsers claim to have
253
+ // localStorage, but calls to getItem() fail.
254
+ result(
255
+ typeof window.localStorage != "undefined" &&
256
+ typeof window.localStorage.getItem == "function"
257
+ )
258
+ }],
259
+
260
+
261
+ // CHECK OUT OUR CONTEXT
262
+
263
+ // Does the device have a MobileSafari-style touch interface?
264
+ // (Here we can now simply follow the wisdom of Gala.)
265
+ //
266
+ ["touch", function () { result(Gala.Pointers.ENV.noMouse); }],
267
+
268
+
269
+ // Is the Reader embedded, or in the top-level window?
270
+ //
271
+ ["embedded", function () { result(top != window.self) }],
272
+
273
+
274
+ // TEST FOR CERTAIN RENDERING OR INTERACTION BUGS
275
+
276
+ // iOS (at least up to version 4.1) makes a complete hash of touch events
277
+ // when an iframe is overlapped by other elements. It's a dog's breakfast.
278
+ // See test/bugs/ios-frame-touch-bug for details.
279
+ //
280
+ ["brokenIframeTouchModel", function () {
281
+ result(Monocle.Browser.iOSVersionBelow("4.2"));
282
+ }],
283
+
284
+ // Webkit-based browsers put floated elements in the wrong spot when
285
+ // columns are used -- they appear way down where they would be if there
286
+ // were no columns. Presumably the float positions are calculated before
287
+ // the columns. A bug has been lodged, and it's fixed in recent WebKits.
288
+ //
289
+ ["floatsIgnoreColumns", function () {
290
+ if (!Monocle.Browser.is.WebKit) { return result(false); }
291
+ var match = navigator.userAgent.match(/AppleWebKit\/([\d\.]+)/);
292
+ if (!match) { return result(false); }
293
+ return result(match[1] < "534.30");
294
+ }],
295
+
296
+ // The latest engines all agree that if a body is translated leftwards,
297
+ // its scrollWidth is shortened. But some older WebKits (notably iOS4)
298
+ // do not subtract translateX values from scrollWidth. In this case,
299
+ // we should not add the translate back when calculating the width.
300
+ //
301
+ ["widthsIgnoreTranslate", function () {
302
+ loadTestFrame(function (fr) {
303
+ var firstWidth = columnedWidth(fr);
304
+ var s = fr.contentDocument.body.style;
305
+ var props = css.toDOMProps("transform");
306
+ for (var i = 0, ii = props.length; i < ii; ++i) {
307
+ s[props[i]] = "translateX(-600px)";
308
+ }
309
+ var secondWidth = columnedWidth(fr);
310
+ for (i = 0, ii = props.length; i < ii; ++i) {
311
+ s[props[i]] = "none";
312
+ }
313
+ result(secondWidth == firstWidth);
314
+ });
315
+ }],
316
+
317
+ // On Android browsers, if the component iframe has a relative width (ie,
318
+ // 100%), the width of the entire browser will keep expanding and expanding
319
+ // to fit the width of the body of the iframe (which, with columns, is
320
+ // massive). So, 100% is treated as "of the body content" rather than "of
321
+ // the parent dimensions". In this scenario, we need to give the component
322
+ // iframe a fixed width in pixels.
323
+ //
324
+ // In iOS, the frame is clipped by overflow:hidden, so this doesn't seem to
325
+ // be a problem.
326
+ //
327
+ ["relativeIframeExpands", function () {
328
+ result(navigator.userAgent.indexOf("Android 2") >= 0);
329
+ }],
330
+
331
+ // iOS3 will pause JavaScript execution if it gets a style-change + a
332
+ // scroll change on a component body. Weirdly, this seems to break GBCR
333
+ // in iOS4.
334
+ //
335
+ ["scrollToApplyStyle", function () {
336
+ result(Monocle.Browser.iOSVersionBelow("4"));
337
+ }],
338
+
339
+
340
+ // TEST FOR OTHER QUIRKY BROWSER BEHAVIOUR
341
+
342
+ // Older versions of WebKit (iOS3, Kindle3) need a min-width set on the
343
+ // body of the iframe at 200%. This forces columns. But when this
344
+ // min-width is set, it's more difficult to recognise 1 page components,
345
+ // so we generally don't want to force it unless we have to.
346
+ //
347
+ ["forceColumns", function () {
348
+ loadTestFrame(function (fr) {
349
+ var bd = fr.contentDocument.body;
350
+ result(bd.className ? true : false);
351
+ });
352
+ }],
353
+
354
+ // A component iframe's body is absolutely positioned. This means that
355
+ // the documentElement should have a height of 0, since it contains nothing
356
+ // other than an absolutely positioned element.
357
+ //
358
+ // But for some browsers (Gecko and Opera), the documentElement is as
359
+ // wide as the full columned content, and the body is only as wide as
360
+ // the iframe element (ie, the first column).
361
+ //
362
+ // It gets weirder. Gecko sometimes behaves like WebKit (not clipping the
363
+ // body) IF the component has been loaded via HTML/JS/Nodes, not URL. Still
364
+ // can't reproduce outside Monocle.
365
+ //
366
+ // FIXME: If we can figure out a reliable behaviour for Gecko, we should
367
+ // use it to precalculate the workaround. At the moment, this test isn't
368
+ // used, but it belongs in src/dimensions/columns.js#columnedDimensions().
369
+ //
370
+ // ["iframeBodyWidthClipped", function () {
371
+ // loadTestFrame(function (fr) {
372
+ // var doc = fr.contentDocument;
373
+ // result(
374
+ // doc.body.scrollWidth <= testFrameSize &&
375
+ // doc.documentElement.scrollWidth > testFrameSize
376
+ // );
377
+ // })
378
+ // }],
379
+
380
+ // Finding the page that a given HTML node is on is typically done by
381
+ // calculating the offset of its rectange from the body's rectangle.
382
+ //
383
+ // But if this information isn't provided by the browser, we need to use
384
+ // node.scrollIntoView and check the scrollOffset. Basically iOS3 is the
385
+ // only target platform that doesn't give us the rectangle info.
386
+ //
387
+ ["findNodesByScrolling", function () {
388
+ result(typeof document.body.getBoundingClientRect !== "function");
389
+ }],
390
+
391
+ // In MobileSafari browsers, iframes are rendered at the width and height
392
+ // of their content, rather than having scrollbars. So in that case, it's
393
+ // the containing element (the "sheaf") that must be scrolled, not the
394
+ // iframe body.
395
+ //
396
+ ["sheafIsScroller", function () {
397
+ loadTestFrame(function (fr) {
398
+ result(fr.parentNode.scrollWidth > testFrameSize);
399
+ });
400
+ }],
401
+
402
+ // For some reason, iOS MobileSafari sometimes loses track of a page after
403
+ // slideOut -- it thinks it has an x-translation of 0, rather than -768 or
404
+ // whatever. So the page gets "stuck" there, until it is given a non-zero
405
+ // x-translation. The workaround is to set a non-zero duration on the jumpIn,
406
+ // which seems to force WebKit to recalculate the x of the page. Weird, yeah.
407
+ //
408
+ ["stickySlideOut", function () {
409
+ result(Monocle.Browser.is.MobileSafari);
410
+ }],
411
+
412
+
413
+ // Chrome and Firefox incorrectly clip text when the dimensions of
414
+ // a page are not an integer. IE10 clips text when the page dimensions
415
+ // are rounded.
416
+ //
417
+ ['roundPageDimensions', function () {
418
+ result(!Monocle.Browser.is.IE);
419
+ }],
420
+
421
+
422
+
423
+ // In IE10, the <html> element of the iframe's document has scrollbars,
424
+ // unless you set its style.overflow to 'hidden'.
425
+ //
426
+ ['documentElementHasScrollbars', function () {
427
+ result(Monocle.Browser.is.IE);
428
+ }],
429
+
430
+
431
+ // Older versions of iOS (<6) would render blank pages if they were
432
+ // off the screen when their layout/position was updated.
433
+ //
434
+ ['offscreenRenderingClipped', function () {
435
+ result(Monocle.Browser.iOSVersionBelow('6'));
436
+ }],
437
+
438
+
439
+ // Gecko is better at loading content with document.write than with
440
+ // javascript: URLs. With the latter, it tends to put cruft in history,
441
+ // and gets confused by <base>.
442
+ ['loadHTMLWithDocWrite', function () {
443
+ result(Monocle.Browser.is.Gecko || Monocle.Browser.is.Opera);
444
+ }]
445
+ ];
446
+
447
+
448
+ function isCompatible() {
449
+ return (
450
+ API.supportsW3CEvents &&
451
+ API.supportsCustomEvents &&
452
+ API.supportsColumns &&
453
+ API.supportsTransform &&
454
+ !API.brokenIframeTouchModel
455
+ );
456
+ }
457
+
458
+
459
+ API.survey = survey;
460
+ API.isCompatible = isCompatible;
461
+
462
+ return API;
463
+ }