monocle-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }