angular / angular.js

AngularJS - HTML enhanced for web apps!
https://angularjs.org
MIT License
58.85k stars 27.52k forks source link

(iOS 8 GM iPhone5C) TypeError: Attempted to assign to readonly property #9128

Closed HAKASHUN closed 8 years ago

HAKASHUN commented 10 years ago

I am using on iOS 8 GM(iPhone5C). With this device, I am getting this error in this line(https://github.com/angular/angular.js/blob/master/src/ng/rootScope.js#L216) But on iOS 8 GM(iPhone5S) and iOS 7.x (iPhone5C, iPhone5S), this error does not occur.

To resolve this error, I rewrote the code as follows:

$new: function(isolate) {
        var child;

        if (isolate) {
          child = new Scope();
          child.$root = this.$root;
          // ensure that there is just one async queue per $rootScope and its children
          child.$$asyncQueue = this.$$asyncQueue;
          child.$$postDigestQueue = this.$$postDigestQueue;
        } else {
          // Only create a child scope class if somebody asks for one,
          // but cache it to allow the VM to optimize lookups.
          if (!this.$$ChildScope) {
            this.$$ChildScope = function ChildScope() {
              this['$$watchers'] = this['$$nextSibling'] =
                  this['$$childHead'] = this['$$childTail'] = null;
              this['$$listeners'] = {};
              this['$$listenerCount'] = {};
              this['$id'] = nextUid();
              this['$$ChildScope'] = null;
            };
            this.$$ChildScope.prototype = this;
          }
          child = new this.$$ChildScope();
        }
        child['this'] = child;
        child['$parent'] = this;
        child['$$prevSibling'] = this.$$childTail;
        if (this.$$childHead) {
          this.$$childTail.$$nextSibling = child;
          this.$$childTail = child;
        } else {
          this.$$childHead = this.$$childTail = child;
        }
        return child;
      },

The situation has improved, but I do not understand the reason...

Narretz commented 10 years ago

Which version of angular did you test? Does it appear simply by including angular or under specific circumstances (your app code)?

HAKASHUN commented 10 years ago

I tested AngularJS v1.2.25.

My app code is below.

page template

...
<card-item-list ng-repeat="card in cards track by $index" data-card="card"></card-item-list>

card-item-list directive

...
var cardItemList = function() {
    var link = function(scope) {};
    return {
      restrict: 'E',
      replace: true,
      templateUrl: 'templates/card/directives/list-item.html',
      scope: {
        card: '=',
      },
      link: link
    };
  };
  cardItemList.$inject = [];
...
<div class="card--list__item">
  <div ng-class="{'card--list__item_state_check': card.isSelect}">
    <card-thumb data-card="card"></card-thumb>
  </div>
  <p class="card--list__item__detail fs_xxs">
    <span ng-if="card.parameter.attack > 0">{{card.parameter.attack}}<br>&nbsp</span>
    <span ng-if="card.parameter.defense > 0">{{card.parameter.defense}}<br>{{card.parameter.speed}}</span>
  </p>
</div>

card-thumb directive

...
var cardThumb = function() {
    return {
      restrict: 'E',
      replace: true,
      transclude: true,
      templateUrl: ''templates/card/directives/card-thumb.html',
      scope: {
        card: '=',
        opts: '=?'
      },
      link: function(scope, element){
      }
    };
  };
 cardThumb.$inject = [];
...
<figure class="thumb thumb_type_1">
  <img class="thumb__img" ng-src="{{card.image_path}}"  ng-if="card.master_id">
  <figcaption class="thumb__caption" ng-transclude></figcaption>
</figure>
caitp commented 9 years ago

@christianv could you please put together a http://plnkr.co/ or http://jsbin.com/ to reproduce this?

I'll see if I can repro this on my ipad or iphone 6 (but, from what you said, it sounds like I might not be able to :()

christianvuerings commented 9 years ago

@caitp I think you meant to notify someone else.

caitp commented 9 years ago

yeah I meant @HAKASHUN, sorry

disney007 commented 9 years ago

remove 'use strict' and try

ssetem commented 9 years ago

we get the same issue, and @HAKASHUN's code fixed the issue for us

shunjikonishi commented 9 years ago

We get the same issue.

I applied @HAKASHUN's code to v1.2.26

https://github.com/shunjikonishi/angular.js/commit/b91f34103974047708145e971506f354f804c17c

When I include angular.js, it works fine. But with angular.min.js, it doesn't work.

I hope to fix this problem with both of normal and minified version.

bennett000 commented 9 years ago

We have also experienced this error with several different iPads, all running iOS 8. I can get exact model numbers if anybody is interested. We have tried builds with Angular versions: 1.2.24, 1.3.0, and 1.3.1

@HAKASHUN's fix has worked at least in debug (non-minified) builds.

@shunjikonishi this is just a guess (I'm dealing with the same issue) but does your minifier perhaps do something like forcing dot notation? That would effectively undo this fix. I've tried one minification with uglify (2.4) and it broke the fix, and now I'm looking into my guess. Perhaps I can ask uglify to respect dot notation, or maybe I'm way off.

update

Is anyone else using $route?

Also @shunjikonishi in my case, uglifyjs was forcing dot notation, effectively undoing the fix. If you're using grunt/uglify it's something like: uglify: { options: { properties: false }}

caitp commented 9 years ago

could you make a quick reproduction of this? I'd like to see it with my phone

bennett000 commented 9 years ago

@caitp I will see what I can do. I've got some unexpected work to take care of, but this is important, so hopefully I will have a plunker by the afternoon.

update

@caitp I made a plunker to attempt to reproduce this issue, and failed.

If anyone is interested in looking at my plunker attempt at reproducing, it's here: http://plnkr.co/edit/EPsxhO1OsD5MmnUyl50t?p=preview I started with a simple click updating a $scope, then tried $route changes (which is where I can consistently reproduce in my app), and then tried repeats, and a repeat in an ng-if.

I will put more time into this later.

shunjikonishi commented 9 years ago

@bennett000 Thank you for advise.

I just used output of grunt package command.

Now, I include uglify angular task in my project. It works fine.

There is one correction. With grunt-contrib-uglify, its option format is like this.

uglify: {
    options: { 
        compress: {
            properties: false 
        }
    }
}
spencerwi commented 9 years ago

This also appears to affect Angular v1.3.0; we've seen it on an iOS8 device in our office, but the page it's on hits the error inconsistently (something we're researching).

Applying @shunjikonishi 's patch causes the page not to break under those conditions, but also causes "undefined" to appear on the page in places it shouldn't, and not to be removed.

Perhaps not the most useful set of details, but this is the result of an initial investigation. We can dig deeper; just wanted to note that v1.3.0 is also affected.

caitp commented 9 years ago

hey guys, we're still waiting for a reproduction =)

caitp commented 9 years ago

(so please provide one)

bennett000 commented 9 years ago

@caitp absolutely, reproducing the error consistently is essential.

What's odd is that I can reproduce this consistently in my application, as can others; yet I failed to reproduce in a plunker. I'm happy to keep working on reproducing this in an example, but I'd really like to get more details from other people who have this issue.

In our case:

iDevice model numbers known to be affected:

I'm wondering if there is a difference if the application is run from the iOS home screen, as opposed to being run in iOS Safari, or in a WebView, or WKView; this has been the case in older iOS versions.

I will work more on a plunker, and I will also take the plunker code, and spin it into a self contained application so I can see if I can reproduce it when "saved" to the iOS home screen.

Again, any extra information anyone else has about their issues would be helpful.

update

Dropping the existing plunkr stuff I had done into a full blown iOS homescreen web app does not seem to have had any additional effect.

Is anyone else using any custom gestures, or doing anything special to avoid things like iOS's bounce scrolling?

dmitriynosenko commented 9 years ago

Removing the 'use strict'; solve the issue for me (iPhone 5C iOS 8.1).

bennett000 commented 9 years ago

Over the last few weeks I continued to fail to reproduce this problem in a controlled environment, like a plunkr. On top of that, other workarounds, including removing 'use strict' do not help me. Obviously without a controlled reproduction of this issue there's nowhere to begin solving this problem. However it occurred to me that Angular ships with its own unit test suite; how do those tests perform on my devices?

Mobile Safari 8.0.0 (iOS 8.1.1) private mocks createMockStyleSheet should allow custom styles to be created and removed when the stylesheet is destroyed FAILED Expected '0px' to be '2px'. ~/Workspaces/angular.js/test/helpers/privateMocksSpec.js:23:47

My guess is that this failed test is unrelated.

Again, anyone with any suggestions on how to reproduce the iOS 8 readonly error would be helpful. I do know other frameworks have had similar issues with readonly property assignments in iOS8.x but they have not helped me reproduce. Here are the links that reference the similar issues some others have encountered

Other steps I've taken to mitigate this problem have been to comb over our entire source, and the source of every library we use to find anything that might set a property read only. This includes grepping for:

Is there anything else that might make JavaScript throw a type error read only? Not being able to reproduce this is not helping my sanity.

m3kka commented 9 years ago

We are also running into this. We were able to reproduce the problem only on the actual device (in our case IPhone 5C) with an IOS version prior to 8.1 (we're testing on 8.0.2 and the problem recurs continuously).

This is quite a serious issue, is there any known workaround?

(We're on Angular 1.3.6, updated today, problem still persists)

pkozlowski-opensource commented 9 years ago

@m3kka did you manage to isolate the problem? It will be very hard to move on this one without narrowing it down first.

m3kka commented 9 years ago

Spent the afternoon looking for a way to reproduce the error in a controlled environment but still no solutions sadly.

I've expanded a bit the workaround of @HAKASHUN so if anyone needs a fast - notSoGood - fix this is what I did.

AngularJS ver. 1.3.6:

RootScope https://github.com/angular/angular.js/blob/master/src/ng/rootScope.js#L208 From:

          if (!this.$$ChildScope) {
            this.$$ChildScope = function ChildScope() {
              this.$$watchers = this.$$nextSibling =
                  this.$$childHead = this.$$childTail = null;
              this.$$listeners = {};
              this.$$listenerCount = {};
              this.$id = nextUid();
              this.$$ChildScope = null;
            };

To:

          if (!this['$$ChildScope']) {
            this['$$ChildScope'] = function ChildScope() {
              this['$$watchers'] = this['$$nextSibling'] =
                  this['$$childHead'] = this['$$childTail'] = null;
              this['$$listeners'] = {};
              this['$$listenerCount'] = {};
              this['$id'] = nextUid();
              this['$$ChildScope'] = null;
            };

https://github.com/angular/angular.js/blob/master/src/ng/rootScope.js#L221 From:

        child.$parent = parent;
        child.$$prevSibling = parent.$$childTail;

To:

        child['$parent'] = parent;
        child['$$prevSibling'] = parent.$$childTail;

Compile https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L1375 From:

transcludedScope.$$transcluded = true;

To

transcludedScope['$$transcluded'] = true;

Until now I am not able to help you with more informations, will keep looking into the issue.

Wasmoo commented 9 years ago

I figured this one out. iOS dies on this line:

              this.$$watchers = this.$$nextSibling =
                  this.$$childHead = this.$$childTail = null;

When I broke this out into separate statements, I found the error was caused when this.$$watchers was assigned first. The following worked correctly:

              this.$$childTail = null;
              this.$$childHead = null;
              this.$$nextSibling = null;
              this.$$watchers = null;

I suspect iOS executes the order of the assignment incorrectly, causing the error. I don't know where else this syntax difference would cause errors, but I suspect it has far reaching implications.

pkozlowski-opensource commented 9 years ago

@Wasmoo wow, this sounds strange. Are you able to isolate this thing in a minimal example, ideally in plunker? If so this would be definitively an issue on the browser side.

Wasmoo commented 9 years ago

@pkozlowski-opensource Unfortunately no. I just ran into the error on the project I'm working on and I have no idea of its source (jQuery throws something at Angular). As it is in my project, you have to click on just the right things in just the right order to get the error to occur, but this and only this change fixes my problem.

Hopefully someone else who is getting the error can confirm the fix.

m3kka commented 9 years ago

We are experiencing this problem in 'heavy' page filled with several scopes and template, simplifying this out in a plunker seems really difficoult :/

I'll try @Wasmoo's fix out, will post back the result on our codebase in a couple minutes.

m3kka commented 9 years ago

Nope @Wasmoo's is not solving the issue in our codebase.

Wasmoo commented 9 years ago

:-( Sadness! I also confirmed that iOS does assignment in this syntax in the correct order:

    j.a = j.b = j.c = 0;

is equivalent to

    j.c = 0;
    j.b = 0;
    j.a = 0;

So that throws that theory out, though I can't explain why my fix works for me.

Wasmoo commented 9 years ago

For what it's worth, my project is still broken - my fix only moved the bug so it fails in the same way at some other point.

m3kka commented 9 years ago

@Wasmoo by applying my workaround you should be able to remove the problem (still you're gonna have to maintain for the next angular versions)

Wasmoo commented 9 years ago

@m3kka Works like magic!

pkozlowski-opensource commented 9 years ago

@m3kka would it mean that Safari on iOS doesn't like dotted access to properties staring with $? Hmm, there must be something additional going on...

jhunken commented 9 years ago

Here's a monkey patch I created to not have to modify the angular.js source code directly. I've been using it for the past few weeks with no more issues. It was based on angular 1.3.3.

https://gist.github.com/jhunken/767cdf62ecc9f31c56da

I also haven't been able to reproduce this issue with a minimal example unfortunately.

chadfawcett commented 9 years ago

If someone is looking for a solution without having to use a patched version of Angular, you could try the following.

I had a couple nested state controllers for ui-router and noticed the error was being triggered by a couple lines that were accessing variables on a parent scope. In an attempt to gain access to these variables without going through the scope I set up my controller using the Controller as syntax. This made it so I could access the variables directly without having to traverse up the scopes.

Not sure if this will work for everyone but my application no longer throws the error.

m3kka commented 9 years ago

@pkozlowski-opensource I think that IOS is somehow messing up with the readonly property on js objects. Apparently accessing the elements with the 'key notation' (can't remember the actual name) ignores this mess up.

Also apparently Angular is not the only one affected by this issue -http://craigsworks.com/projects/forums/showthread.php?tid=3649

@jhunken You made me bash my head. Should have used decorators from the beginning. Thank you :)

ozen commented 9 years ago

I have the same problem. Suggested workaround seems to solved it. I use @jhunken's decorator.

Noob question: what does this issue being in purgatory milestone mean? It doesn't sound good.

bennett000 commented 9 years ago

I still have yet to reproduce this, and I have wasted a lot of time trying. I am however happy to say that @jhunken 's monkey patch seems to have solved my problems for now. I look forward to figuring out why this works since I squandered so much time stepping through this process over/over without getting any results.

jerry4 commented 9 years ago

Adding some details that probably are not useful on their own, but... On iOS 8.1.2 iPad 2 and mini I have seen the "TypeError: Attempted to assign to readonly property". It's a large app, so hard to get into plunkr.

We had the issue appearing consistently using Angular 1.3.7 (we had removed 'use strict' from the min file for v1.3 to fix this same problem, but didn't remove it after the upgrade). Last time the error was popping up in a different place in the app and was less consistent. This time we had repro steps to do it on demand. Removing 'use strict' from angular.min.js fixed the issue. The final step before the error involved changing the route (we use the standard router: ngRoute). In our case this resulted in a data grid with no data and the messages weren't translated (using the translate directive from pascalprecht.translate). Switching the route again using the same dropdown or refreshing would get the page working again.

I do believe this is a mobile safari bug because we do not see it in any other browser.

edzis commented 9 years ago

This seems to be an iOS 8 bug - http://stackoverflow.com/a/27401601

On bugsnag error logs for my application I also see that only on Mobile safari with iOS 8.0 and 8.1

Below is a stack trace for angular 1.2.28. The same issue is there also for 1.2.24.

TypeError Attempted to assign to readonly property.
    angular.js:12188:37 $$childScopeClass
    angular.js:12198:8 prototype
    angular.js:6161:29 
    angular.js:6773:35 controllersBoundTransclude
    angular.js:21409:16 link
    angular.js:6752:19 nodeLinkFn
    angular.js:6146:23 compositeLinkFn
    angular.js:6746:35 nodeLinkFn
    angular.js:6954:36 
    angular.js:8171:13 
    angular.js:11682:81 wrappedCallback
    angular.js:11768:34 
    angular.js:12811:28 prototype
    angular.js:12623:36 prototype
    angular.js:12915:12 prototype
    angular.js:19264:29 
    angular.js:2853:14 
    angular.js:325:22 forEach
    angular.js:2852:12 eventHandler
malterb commented 9 years ago

Is there any other fix for this besides removing 'use strict'? I would like to CDN angular and this would disable it...

edit: using gist works. sorry about that

DropsOfSerenity commented 9 years ago

Monkey patching worked for me too, Obviously not a good solution, and this is a bug that is breaking angular out in production. Looks like ios8 webkit really dislikes assignment of .$$variables

For some info, I had to patch compile.js as my directives compile phase was being borked.

transcludedScope['$$transcluded'] = true;
Nivani commented 9 years ago

Making some changes from dot notation to brackets notation by monkey patching like @jhunken did worked for me too. Very strange problem.

m3kka commented 9 years ago

Still no word about an official solution?

rjbernaldo commented 9 years ago

Removing use strict did it for me. Still waiting on the official solution though.

hamstu commented 9 years ago

@rjbernaldo To clarify, are you removing 'use strict' from angular.js, your own source files, or both?

rjbernaldo commented 9 years ago

@hamstu I removed it from angular.js only.

booleanbetrayal commented 9 years ago

Removing 'use strict'; fixed the issue on my end as well. I'm fairly convinced Apple is trying to kill off web-apps with breakage like this. =]

pascalc commented 9 years ago

Removing 'use strict'; did stop the crashes but seems to have caused another problem: now my app seems to hang instead of crashing, and the tab in Safari becomes totally unresponsive. This even happens in the iOS simulator, and the simulator process starts using 200% CPU on my mac.

Has this happened to anyone else?

caitp commented 9 years ago

could you post a stack trace of the crash, along with versions of al the libraries you're using? that would be helpfulp.

caitp commented 9 years ago

oh hang on, I see what it is... wow I don't think that's the right behaviour in JSC, but just to check...

caitp commented 9 years ago

I haven't been able to reproduce this on my iphone, ipad, or any of the ios simulators I've tried. It looks to me like the issue is the prototype property being incorrectly defined as non-writable in JSC, which would be a bug (and, would explain why it's no longer reproducible if they've fixed it). If that's not the bug being hit, then please supply a reproduction for us.